feat(core): 模型选择框换 restraint chip 下拉 + worker 并发出图(修 -P solo 串行)
UI:脚本助手模型框从『原生 select + inline style』换成复用设计系统的 chip 下拉(向上展开,新增共享 .align-up 变体),去 inline、合规范。并发:本地 worker 从 -P solo(单任务串行=一次只生成一张)改 -P threads -c 4;生产 k8s worker --concurrency 2→4。基础资产/故事板/视频出图可并行。loading 动画基础设施(卡内+全局 spinner)已验证存在。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
edaa314261
commit
03e7949307
@@ -19,7 +19,7 @@ Start workers in separate terminals:
|
||||
```bash
|
||||
cd /Users/maidong/Desktop/zyc/qiyuan_gitea/AirShelf/core/backend
|
||||
source .venv/bin/activate
|
||||
celery -A airshelf worker -l info
|
||||
celery -A airshelf worker -l info -P threads -c 4 # 线程池 4 并发(出图/视频可并行;勿用 -P solo 串行)
|
||||
```
|
||||
|
||||
`ffmpeg` must be available on `PATH` for Stage5 export jobs.
|
||||
|
||||
@@ -869,6 +869,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
|
||||
}
|
||||
.chip-wrap.open .chip-menu { display: block; }
|
||||
.chip-menu.align-right { left: auto; right: 0; }
|
||||
.chip-menu.align-up { top: auto; bottom: calc(100% + 4px); }
|
||||
.chip-menu .mi {
|
||||
height: 32px; padding: 0 10px;
|
||||
font-size: 13px; color: var(--accent-black);
|
||||
|
||||
@@ -842,6 +842,16 @@ export function PipelinePage(props: {
|
||||
// 脚本模型下拉:用户可选 豆包/GPT-5.5/Gemini(空 = 用后端默认文本模型)
|
||||
const [scriptModelId, setScriptModelId] = useState<string>("");
|
||||
const activeScriptModelId = scriptModelId || textModels?.[0]?.id || "";
|
||||
// 模型选择小按钮(输入框下方)· 自建 restraint 下拉(幽灵触发 + popover 菜单),不再用原生 select + inline
|
||||
const [modelMenuOpen, setModelMenuOpen] = useState(false);
|
||||
const activeModel = textModels?.find((m) => m.id === activeScriptModelId);
|
||||
const activeModelName = activeModel?.display_name || activeModel?.name || "默认模型";
|
||||
useEffect(() => {
|
||||
if (!modelMenuOpen) return;
|
||||
const close = (e: MouseEvent) => { if (!(e.target as HTMLElement).closest(".chat-model-pick")) setModelMenuOpen(false); };
|
||||
document.addEventListener("click", close);
|
||||
return () => document.removeEventListener("click", close);
|
||||
}, [modelMenuOpen]);
|
||||
// 删除分镜的两步确认(行内变红,3 秒不二次点击自动复位;不用原生 confirm)
|
||||
const [armedDelete, setArmedDelete] = useState<string | null>(null);
|
||||
// 行35 · 单条分镜重跑 / 删除的即时反馈:正在处理的 shot id(按钮转「处理中」并禁用)
|
||||
@@ -2267,17 +2277,24 @@ export function PipelinePage(props: {
|
||||
<button className="chat-icon-btn" id="chat-upload-btn" type="button" title="上传脚本附件" aria-label="上传脚本附件" onClick={() => chatFileRef.current?.click()}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14M5 12h14" /></svg>
|
||||
</button>
|
||||
{/* 模型选择小按钮(放输入框下方,对齐 ChatGPT/Lovart) */}
|
||||
{/* 模型选择小按钮(输入框下方,对齐 ChatGPT/Lovart)· 复用 restraint chip 下拉,向上展开 */}
|
||||
{textModels && textModels.length > 0 ? (
|
||||
<select
|
||||
className="setup-select"
|
||||
style={{ height: 26, fontSize: 12, padding: "0 6px", maxWidth: 150, marginLeft: 6 }}
|
||||
value={activeScriptModelId}
|
||||
onChange={(event) => setScriptModelId(event.target.value)}
|
||||
title="选择脚本生成模型(豆包 / GPT / Gemini)"
|
||||
>
|
||||
{textModels.map((m) => <option key={m.id} value={m.id}>{m.display_name || m.name}</option>)}
|
||||
</select>
|
||||
<div className={`chip-wrap${modelMenuOpen ? " open" : ""}`}>
|
||||
<button type="button" className="chip" title="选择脚本生成模型(豆包 / GPT / Gemini)" onClick={() => setModelMenuOpen((v) => !v)}>
|
||||
{activeModelName}
|
||||
<svg className="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div className="chip-menu align-up">
|
||||
{textModels.map((m) => (
|
||||
<div key={m.id} className={`mi${m.id === activeScriptModelId ? " selected" : ""}`} role="menuitemradio" aria-checked={m.id === activeScriptModelId} tabIndex={0}
|
||||
onClick={() => { setScriptModelId(m.id); setModelMenuOpen(false); }}
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setScriptModelId(m.id); setModelMenuOpen(false); } }}>
|
||||
{m.display_name || m.name}
|
||||
<svg className="mi-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<span className="spacer"></span>
|
||||
<button className="chat-send-btn" id="chat-send-btn" type="button" title="发送" aria-label="发送" disabled={loading || (!setupOpen && !chatText.trim() && pendingTagEdits.length === 0)} onClick={submitChat}>
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
# Celery worker connects to the external (Volcano managed) Redis broker
|
||||
# configured via the airshelf-core-env secret. Uses `args` (not `command`)
|
||||
# so the image ENTRYPOINT still runs but skips migrate/collectstatic ($1=celery).
|
||||
args: ["celery", "-A", "airshelf.celery:app", "worker", "-l", "info", "--concurrency", "2"]
|
||||
args: ["celery", "-A", "airshelf.celery:app", "worker", "-l", "info", "--concurrency", "4"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: airshelf-core-env
|
||||
|
||||
@@ -15,7 +15,8 @@ cd core/backend
|
||||
.venv/Scripts/python.exe manage.py migrate # 跑新迁移(0005/0006 + projects 0002)
|
||||
.venv/Scripts/python.exe manage.py runserver 0.0.0.0:8010
|
||||
# 另起 celery worker(图片/故事板/视频是异步,靠 worker 执行;没 worker 这几趴会卡)
|
||||
.venv/Scripts/python.exe -m celery -A airshelf worker -l info -P solo
|
||||
# -P threads -c 4 = 线程池 4 并发(Windows 不能用 prefork;-P solo 是单任务串行=一次只生成一张,别再用 solo)
|
||||
.venv/Scripts/python.exe -m celery -A airshelf worker -l info -P threads -c 4
|
||||
|
||||
# 前端(端口 5173,vite 代理 /api → 8010)
|
||||
cd core/frontend
|
||||
|
||||
Reference in New Issue
Block a user