生图模型可选(火山/gpt-image)+ 模特上身图提示词强化 + 多会话等改动

本轮(生图模型选择 + 火山接入):
- 工作室新增「生图模型」选择器(模特上身图/平台套图头部 chip + 图片创作底部 Pill),
  默认火山 Seedream,可切 gpt-image-2;选择写入 localStorage,下次进页面读回
- 后端 resolve_image_model 解析所选模型;enqueue_standalone_images 接 image_model
- worker 按模型能力分流:有 image_edit(gpt-image)走多图编辑;无(火山)走
  image_generation(image=参考图);新增 _ratio_to_volcano_size 让火山按比例出图
  → 内衣等敏感品类用火山可绕开 gpt-image 的 sexual 内容审核

模特上身图提示词:
- 穿戴/非穿戴分流、按 index 变化动作场景镜头、负面词尾接、多图参考序号自适应
- _product_reference_urls:商品参考真实上传图优先、排除 AI 生成图、可多张

其他(并入此前各会话未提交改动):
- 图片创作多会话(ImageConversation + migration 0019)、任务中心按类型过滤
- accounts/projects/assets/team/auth 等零散调整、相关测试
- 测试脚本(tryon_*.py)、测试清单(core/bug/*.xlsx)
This commit is contained in:
zyc
2026-06-27 14:00:28 +08:00
parent 0a519ca892
commit ce7eaefe1d
21 changed files with 1279 additions and 158 deletions
+17 -8
View File
@@ -29,14 +29,17 @@ const SHELL_COMMANDS: Command[] = [
{ id: "image-optimize", group: "常用动作", label: "图片创作", sub: "对话式生成、编辑、加入资产库", page: "imageOptimize", icon: "images" }
];
function CommandPalette({ open, onClose, navigate }: { open: boolean; onClose: () => void; navigate: Navigate }) {
function CommandPalette({ open, onClose, navigate, canManageBilling = true }: { open: boolean; onClose: () => void; navigate: Navigate; canManageBilling?: boolean }) {
const [query, setQuery] = useState("");
useBodyScrollLock(open);
useEffect(() => { if (open) setQuery(""); }, [open]);
const items = useMemo(() => {
const q = query.trim().toLowerCase();
return SHELL_COMMANDS.filter((cmd) => !q || [cmd.label, cmd.sub, cmd.group, cmd.id].join(" ").toLowerCase().includes(q));
}, [query]);
// 非主账号:命令面板里也不暴露「团队」「消费」(与侧栏一致,PMC#3)
return SHELL_COMMANDS
.filter((cmd) => canManageBilling || (cmd.page !== "team" && cmd.page !== "account"))
.filter((cmd) => !q || [cmd.label, cmd.sub, cmd.group, cmd.id].join(" ").toLowerCase().includes(q));
}, [query, canManageBilling]);
const run = (cmd: Command) => { onClose(); navigate(cmd.page); };
if (!open) return null;
let lastGroup = "";
@@ -109,13 +112,14 @@ const ACCOUNT_ITEMS: { act: Page; icon: string; label: string }[] = [
{ act: "account", icon: "creditCard", label: "消费与余额" }
];
function AccountMenu({ anchorRect, onClose, navigate, logout, user, team }: {
function AccountMenu({ anchorRect, onClose, navigate, logout, user, team, canManageBilling = true }: {
anchorRect: DOMRect;
onClose: () => void;
navigate: Navigate;
logout: () => void;
user: User;
team: Team | null;
canManageBilling?: boolean;
}) {
const menuRef = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState<{ left: number; top: number }>({ left: anchorRect.left, top: anchorRect.bottom + 8 });
@@ -169,7 +173,7 @@ function AccountMenu({ anchorRect, onClose, navigate, logout, user, team }: {
<span className="mail">{user.username}</span>
</span>
</div>
{ACCOUNT_ITEMS.map((item) => (
{ACCOUNT_ITEMS.filter((item) => canManageBilling || (item.act !== "team" && item.act !== "account")).map((item) => (
<button key={item.act} type="button" role="menuitem" onClick={() => go(item.act)}>
<IconKitSvg name={item.icon} />
{item.label}
@@ -222,11 +226,13 @@ const PAGE_TO_NAV: Partial<Record<Page, Page>> = {
settingsNotify: "settings"
};
export function Sidebar({ page, navigate, user, team, products, projects, productTotal, projectTotal, logout, onOpenAdmin }: {
export function Sidebar({ page, navigate, user, team, canManageBilling = true, products, projects, productTotal, projectTotal, logout, onOpenAdmin }: {
page: Page;
navigate: Navigate;
user: User;
team: Team | null;
// 主账号(owner=超管)才看得到「团队」「消费」入口;子账号隐藏(PMC#3)。默认 true 兼容旧调用。
canManageBilling?: boolean;
products: Product[];
projects: Project[];
productTotal?: number;
@@ -238,6 +244,8 @@ export function Sidebar({ page, navigate, user, team, products, projects, produc
onOpenAdmin?: () => void;
}) {
const activeNav = PAGE_TO_NAV[page];
// 子账号:导航里去掉「团队」「消费」两项(命令面板、账户菜单同步过滤)。
const navItems = NAV.filter((item) => canManageBilling || (item.page !== "team" && item.page !== "account"));
// 徽标用后端真实总数(分页 count),不用已加载的页内条数
const badges: Partial<Record<string, number>> = { products: productTotal ?? products.length, projects: projectTotal ?? projects.length };
const avatar = (team?.name || user.username || "A").slice(0, 1).toUpperCase();
@@ -320,7 +328,7 @@ export function Sidebar({ page, navigate, user, team, products, projects, produc
</div>
<div className="nav-section"></div>
<nav>
{NAV.map((item) => (
{navItems.map((item) => (
<a
key={item.id}
href={`/${item.id}`}
@@ -367,7 +375,7 @@ export function Sidebar({ page, navigate, user, team, products, projects, produc
</div>
</div>
</aside>
<CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} navigate={navigate} />
<CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} navigate={navigate} canManageBilling={canManageBilling} />
{accountAnchor && (
<AccountMenu
anchorRect={accountAnchor}
@@ -376,6 +384,7 @@ export function Sidebar({ page, navigate, user, team, products, projects, produc
logout={handleLogout}
user={user}
team={team}
canManageBilling={canManageBilling}
/>
)}
</>