优化图片创作

This commit is contained in:
Azmat@qq.com
2026-09-15 11:54:00 +08:00
parent f15fe21b1a
commit 99a126d19b
11 changed files with 206 additions and 61 deletions
+3
View File
@@ -64,6 +64,9 @@ IMAGE_PRESETS: dict[str, str] = {
"黑金高级": "黑金奢华商业摄影,深黑背景,金色高光,低调曝光,高对比度,强烈明暗塑形,精准轮廓光,金属反射质感,高级镜面反射,深邃黑位,暖金色点缀,低饱和、精致、克制、奢华,高端广告摄影质感。",
"冷峻科技": "现代科技商业摄影,冷灰蓝色调,低饱和,高级冷色温,干净硬朗的光线,精准边缘光,金属与玻璃质感清晰,适度高对比,深灰阴影,简洁现代,锐利清晰但不过度数字化,真实产品摄影质感,理性、专业、克制,避免科幻全息、机器人和过度AI视觉元素。",
"赛博朋克": "赛博朋克摄影风格,蓝紫与洋红霓虹色调,高饱和霓虹光,强烈冷暖色彩碰撞,高对比,深黑阴影,彩色轮廓光,霓虹反射,局部RGB光效,未来都市视觉语言,强烈视觉冲击,暗部层次丰富,真实摄影质感,科技感与潮流感结合,避免雨天效果。",
"日系手绘动画": "经典日系手绘动画电影质感,柔和自然配色,清新青绿与天空蓝色调,温暖阳光,细腻手绘线条,水彩与赛璐璐结合质感,柔和阴影,自然光影,丰富植物细节,轻微纸张与颜料肌理,童话般自然环境,宁静治愈,清新、温暖、诗意、生活感,具有传统二维动画电影质感,避免3D塑料感、过度数字化和强烈锐化。",
"国风水墨": "中国传统水墨画风格,黑白灰墨色体系,淡青与赭石少量点染,水墨晕染,浓淡干湿变化,焦墨、淡墨与泼墨结合,宣纸自然肌理,山水皴法,虚实相生,大面积留白,云雾朦胧,柔和层次,写意而不过度抽象,空灵、含蓄、诗意、东方禅意,强调墨韵与意境,避免高饱和、现代摄影感和西式油画质感。",
"新中式": "现代新中式东方美学,传统中国元素与现代极简设计融合,黑白灰、原木色、墨绿与低饱和暖色体系,大面积留白,克制高级配色,简洁几何构图,中式格栅、山石、竹、松、水景等东方元素适度点缀,柔和自然光,细腻材质,现代空间感与传统意境结合,宋式审美,安静、雅致、克制、高级,商业视觉质感,避免大红大金、复杂雕花和俗艳传统元素。",
}
# 来源于「图片创作预设.docx」的统一生成收束。用户界面只展示预设标题;
+10 -2
View File
@@ -1310,12 +1310,20 @@ class PresetGuidanceTests(CreationAgentBaseTests):
self.assertIn("避免过度HDR", prompt)
self.assertEqual(apply_image_preset_prompt("电影感", prompt), prompt)
def test_new_image_preset_style_is_added_to_the_actual_image_prompt(self):
from .creation_presets import apply_image_preset_prompt
prompt = apply_image_preset_prompt("国风水墨", "画一只白瓷茶杯")
self.assertIn("【图片风格预设】国风水墨", prompt)
self.assertIn("中国传统水墨画风格", prompt)
self.assertIn("避免高饱和、现代摄影感", prompt)
def test_every_frontend_preset_has_guidance(self):
"""前端 9 个视频 + 9 个图片预设都要有拍法,漏一个就等于那张卡是摆设。"""
"""前端 9 个视频 + 12 个图片预设都要有拍法,漏一个就等于那张卡是摆设。"""
from .creation_presets import IMAGE_PRESETS, VIDEO_PRESETS
self.assertEqual(len(VIDEO_PRESETS), 9)
self.assertEqual(len(IMAGE_PRESETS), 9)
self.assertEqual(len(IMAGE_PRESETS), 12)
self.assertTrue(all(text.strip() for text in {**VIDEO_PRESETS, **IMAGE_PRESETS}.values()))
Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+170 -37
View File
@@ -156,9 +156,11 @@
.login-page .login-form-panel {
position: relative;
z-index: auto;
overflow: hidden;
overflow: auto;
display: grid;
place-items: center;
align-content: safe center;
justify-items: center;
gap: 16px;
padding: clamp(48px, 6vw, 106px);
background: transparent;
}
@@ -203,7 +205,8 @@
position: relative;
z-index: 2;
width: min(100%, 520px);
height: 548px;
height: auto;
min-height: 0;
padding: 44px 46px 38px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.28);
@@ -452,11 +455,12 @@
.login-page .login-submit {
height: 54px;
min-width: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 2px;
border: 0;
border-radius: 11px;
color: #fff;
@@ -478,51 +482,130 @@
filter: saturate(0.85);
}
.login-page .login-submit:disabled svg { animation: login-spin 800ms linear infinite; }
.login-page .login-submit svg { width: 17px; height: 17px; }
.login-page .login-submit svg { width: 17px; height: 17px; flex: 0 0 auto; }
.login-page .login-extras {
position: absolute;
z-index: 2;
left: 50%;
bottom: 28px;
width: min(calc(100% - 48px), 520px);
display: grid;
gap: 10px;
color: rgba(255, 255, 255, 0.48);
font-size: 12px;
transform: translateX(-50%);
}
.login-page .login-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
color: rgba(255, 255, 255, 0.48);
font-size: 12px;
}
.login-page .login-row label {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
cursor: pointer;
}
.login-page .login-row input[type="checkbox"] {
width: 14px;
height: 14px;
flex: 0 0 auto;
accent-color: #7da1ff;
}
.login-page .login-row a,
.login-page .login-switch a {
color: rgba(183, 204, 255, 0.88);
text-decoration: none;
white-space: nowrap;
}
.login-page .login-row a:hover,
.login-page .login-switch a:hover { text-decoration: underline; }
/* 三入口操作条:同宽收进卡片,小屏改纵向避免撑破 */
.login-page .login-actions {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr);
align-items: stretch;
gap: 0;
min-width: 0;
width: 100%;
max-width: 100%;
overflow: hidden;
border-radius: 11px;
background: linear-gradient(135deg, #002d9f, #001d70 76%);
box-shadow: 0 14px 30px rgba(0, 25, 102, 0.32);
}
.login-page .login-actions .login-submit {
margin-top: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.login-page .login-actions .login-submit:hover:not(:disabled) {
transform: none;
filter: brightness(1.08);
box-shadow: none;
}
.login-page .login-action-alt {
min-width: 0;
height: 54px;
padding: 0 8px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border: 0;
border-radius: 0;
color: rgba(255, 255, 255, 0.88);
background: transparent;
font: inherit;
font-size: 12px;
line-height: 1.2;
cursor: pointer;
}
.login-page .login-action-alt + .login-submit,
.login-page .login-submit + .login-action-alt {
box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.16);
}
.login-page .login-action-alt span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.login-page .login-action-alt:hover { background: rgba(255, 255, 255, 0.08); }
.login-page .login-action-alt svg { width: 14px; height: 14px; flex: 0 0 auto; }
.login-page .login-switch {
margin: 18px 0 0;
text-align: center;
color: rgba(255, 255, 255, 0.48);
font-size: 12px;
}
.login-page .login-card-footer {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
margin-top: 14px;
color: rgba(255, 255, 255, 0.54);
font-size: 10px;
}
.login-page .login-card-footer svg { width: 13px; height: 13px; }
/* 注册页仍可能引用旧 extras 容器,保留文档流兜底 */
.login-page .login-extras {
position: static;
z-index: 2;
width: min(100%, 520px);
display: grid;
gap: 10px;
color: rgba(255, 255, 255, 0.48);
font-size: 12px;
transform: none;
}
.login-page .login-sso {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
min-width: 0;
}
.login-page .login-sso button {
min-width: 0;
height: 36px;
display: inline-flex;
align-items: center;
@@ -537,24 +620,7 @@
cursor: pointer;
}
.login-page .login-sso button:hover { background: rgba(255, 255, 255, 0.08); }
.login-page .login-sso svg { width: 14px; height: 14px; }
.login-page .login-switch {
text-align: center;
color: rgba(255, 255, 255, 0.48);
font-size: 12px;
}
.login-page .login-card-footer {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
margin-top: 26px;
color: rgba(255, 255, 255, 0.54);
font-size: 10px;
}
.login-page .login-card-footer svg { width: 13px; height: 13px; }
.login-page .login-sso svg { width: 14px; height: 14px; flex: 0 0 auto; }
.login-page .login-note {
margin: 0;
@@ -649,6 +715,9 @@
}
.login-page .login-visual { display: none; }
.login-page .login-form-panel {
align-content: start;
justify-items: center;
gap: 16px;
overflow: auto;
padding: 32px 20px 28px;
}
@@ -674,6 +743,70 @@
position: static;
width: min(100%, 480px);
transform: none;
margin-top: 16px;
margin-top: 0;
}
.login-page .login-card-heading { margin-bottom: 28px; }
.login-page .login-form { gap: 14px; }
}
/* 窄屏:主登录在上,两侧入口两列在下 */
@media (max-width: 560px) {
.login-page .login-form-panel {
padding: 24px 14px 20px;
}
.login-page .login-card {
width: min(100%, 480px);
padding: 28px 16px 22px;
}
.login-page .login-actions {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
background: transparent;
box-shadow: none;
overflow: visible;
}
.login-page .login-actions .login-submit {
grid-column: 1 / -1;
order: -1;
border-radius: 11px;
background: linear-gradient(135deg, #002d9f, #001d70 76%);
box-shadow: 0 14px 30px rgba(0, 25, 102, 0.32);
}
.login-page .login-actions .login-submit:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 18px 36px rgba(0, 23, 94, 0.4);
filter: brightness(1.06);
}
.login-page .login-action-alt,
.login-page .login-action-alt + .login-submit,
.login-page .login-submit + .login-action-alt {
box-shadow: none;
}
.login-page .login-action-alt {
height: 44px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 10px;
background: rgba(255, 255, 255, 0.045);
}
.login-page .login-row {
flex-wrap: wrap;
gap: 8px 12px;
}
}
/* 矮屏:顶部对齐可滚动,避免辅助入口与主按钮重叠 */
@media (max-height: 800px) {
.login-page .login-form-panel {
align-content: start;
gap: 16px;
overflow-y: auto;
padding-top: 28px;
padding-bottom: 24px;
}
.login-page .login-card {
height: auto;
min-height: 0;
}
.login-page .login-card-heading { margin-bottom: 24px; }
.login-page .login-form { gap: 14px; }
}
+11 -13
View File
@@ -552,14 +552,6 @@ export function AuthScreen({
<p className={`login-error${error ? " show" : ""}`} role="alert">
{error || "账号或密码不正确,请重新输入。"}
</p>
<button className="login-submit" type="submit" disabled={busy}>
{busy ? (LOGIN_PROGRESS_COPY[loginProgress || "checking"] || "正在进入工作台") : "确认登录"}
{busy ? <LoaderCircle /> : <ArrowRight />}
</button>
</form>
<p className="login-card-footer"><Lock /></p>
</div>
<div className="login-extras">
<div className="login-row">
<label>
<input type="checkbox" checked={remember} onChange={(event) => setRemember(event.target.checked)} />
@@ -567,17 +559,23 @@ export function AuthScreen({
</label>
<a href="#" onClick={(event) => { event.preventDefault(); showToast("重置密码", "请联系团队超管重置你的登录密码"); }}>?</a>
</div>
<div className="login-sso">
<button type="button" onClick={() => showToast("微信扫码", "对接中 · 当前请用账号 + 密码登录")}>
<ScanLine />
<div className="login-actions">
<button type="button" className="login-action-alt" onClick={() => showToast("微信扫码", "对接中 · 当前请用账号 + 密码登录")}>
<ScanLine /> <span></span>
</button>
<button type="button" onClick={() => showToast("飞书 SSO", "对接中 · 当前请用账号 + 密码登录")}>
<ShieldCheck /> SSO
<button className="login-submit" type="submit" disabled={busy}>
{busy ? (LOGIN_PROGRESS_COPY[loginProgress || "checking"] || "正在进入工作台") : "确认登录"}
{busy ? <LoaderCircle /> : <ArrowRight />}
</button>
<button type="button" className="login-action-alt" onClick={() => showToast("飞书 SSO", "对接中 · 当前请用账号 + 密码登录")}>
<ShieldCheck /> <span> SSO</span>
</button>
</div>
</form>
<p className="login-switch">
? <a href="/register" onClick={(event) => { event.preventDefault(); switchMode("register"); }}> </a>
</p>
<p className="login-card-footer"><Lock /></p>
</div>
</main>
{toastNode}
+3
View File
@@ -59,6 +59,9 @@ const IMAGE_PRESETS: PresetItem[] = [
{ name: "黑金高级", category: "style", mode: "image", title: "黑金高级", cover: "/assets/image-presets/black-gold.png" },
{ name: "冷峻科技", category: "style", mode: "image", title: "冷峻科技", cover: "/assets/image-presets/tech-cool.png" },
{ name: "赛博朋克", category: "style", mode: "image", title: "赛博朋克", cover: "/assets/image-presets/cyberpunk.png" },
{ name: "日系手绘动画", category: "style", mode: "image", title: "日系手绘动画", cover: "/assets/image-presets/japanese-hand-drawn-anime.png" },
{ name: "国风水墨", category: "style", mode: "image", title: "国风水墨", cover: "/assets/image-presets/chinese-ink-wash.png" },
{ name: "新中式", category: "style", mode: "image", title: "新中式", cover: "/assets/image-presets/neo-chinese.png" },
];
const VIDEO_FILTERS = [