feat(ai-studio): streamline project workspace
This commit is contained in:
+104
-58
@@ -238,7 +238,7 @@ class ImageStudioPreviewDialog(QDialog):
|
||||
class ImageStudioTab(QWidget):
|
||||
"""Sixth tab: project-based AI image studio."""
|
||||
|
||||
PROJECT_COLUMNS = ["项目", "账号", "商品ID", "更新时间"]
|
||||
PROJECT_COLUMNS = ["店铺", "商品ID", "更新时间"]
|
||||
ORIGINAL_COLUMNS = ["序号", "状态", "远程地址"]
|
||||
POOL_COLUMNS = ["类型", "比例", "状态", "来源", "本地文件"]
|
||||
JOB_STATUS_LABELS = {
|
||||
@@ -277,6 +277,7 @@ class ImageStudioTab(QWidget):
|
||||
self.selected_source_asset_id = None
|
||||
self._running_worker = None
|
||||
self._running_thread = None
|
||||
self._operation_running = False
|
||||
self._worker_error_handled = False
|
||||
self._download_open_after = {}
|
||||
|
||||
@@ -321,31 +322,30 @@ class ImageStudioTab(QWidget):
|
||||
self.item_id_edit.setObjectName("imageStudioItemIdEdit")
|
||||
self.item_id_edit.setPlaceholderText("商品ID")
|
||||
self.item_id_edit.setMinimumWidth(160)
|
||||
self.open_project_button = QPushButton("打开商品项目")
|
||||
self.open_project_button.setObjectName("imageStudioOpenProjectButton")
|
||||
|
||||
self.current_project_label = QLabel("未打开商品项目")
|
||||
self.current_project_label = QLabel("未选择商品")
|
||||
self.current_project_label.setObjectName("imageStudioCurrentProjectLabel")
|
||||
self.current_project_label.setMinimumWidth(220)
|
||||
self.autosave_label = QLabel("选择账号并输入商品ID后打开")
|
||||
self.autosave_label = QLabel("选择店铺并输入商品ID后拉取")
|
||||
self.autosave_label.setObjectName("imageStudioAutosaveLabel")
|
||||
|
||||
self.pull_images_button = QPushButton("拉取蝦皮主图")
|
||||
self.pull_images_button.setObjectName("imageStudioPullImagesButton")
|
||||
self.open_folder_button = QPushButton("打开项目文件夹")
|
||||
self.open_folder_button.setObjectName("imageStudioOpenFolderButton")
|
||||
self.delete_project_button = QPushButton("删除项目")
|
||||
self.delete_project_button.setObjectName("imageStudioDeleteProjectButton")
|
||||
|
||||
layout.addWidget(QLabel("账号"))
|
||||
layout.addWidget(self.account_combo)
|
||||
layout.addWidget(QLabel("商品ID"))
|
||||
layout.addWidget(self.item_id_edit)
|
||||
layout.addWidget(self.open_project_button)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(self.current_project_label, 1)
|
||||
layout.addWidget(self.pull_images_button)
|
||||
layout.addWidget(self.autosave_label)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(self.pull_images_button)
|
||||
layout.addWidget(self.open_folder_button)
|
||||
layout.addWidget(self.delete_project_button)
|
||||
return panel
|
||||
|
||||
def _build_project_panel(self):
|
||||
@@ -355,7 +355,7 @@ class ImageStudioTab(QWidget):
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(8)
|
||||
|
||||
title = QLabel("商品项目")
|
||||
title = QLabel("商品列表")
|
||||
title.setObjectName("imageStudioSectionTitle")
|
||||
hint = QLabel("点击切换项目,项目会自动保存")
|
||||
hint.setObjectName("imageStudioMutedLabel")
|
||||
@@ -382,7 +382,7 @@ class ImageStudioTab(QWidget):
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(8)
|
||||
|
||||
self.workspace_empty_label = QLabel("选择账号并输入商品ID,点击「打开商品项目」开始。")
|
||||
self.workspace_empty_label = QLabel("选择店铺并输入商品ID后,点击「拉取蝦皮主图」开始。")
|
||||
self.workspace_empty_label.setObjectName("imageStudioWorkspaceEmptyLabel")
|
||||
self.workspace_empty_label.setWordWrap(True)
|
||||
layout.addWidget(self.workspace_empty_label)
|
||||
@@ -635,6 +635,9 @@ class ImageStudioTab(QWidget):
|
||||
background: #eef6ff;
|
||||
color: #24292f;
|
||||
}
|
||||
#imageStudioDeleteProjectButton {
|
||||
color: #b42318;
|
||||
}
|
||||
QTableWidget#imageStudioOriginalTable,
|
||||
QTableWidget#imageStudioPoolTable,
|
||||
QListWidget#imageStudioMainSelectionList,
|
||||
@@ -650,9 +653,9 @@ class ImageStudioTab(QWidget):
|
||||
)
|
||||
|
||||
def _connect_signals(self):
|
||||
self.open_project_button.clicked.connect(self.open_project)
|
||||
self.pull_images_button.clicked.connect(self.pull_main_images)
|
||||
self.open_folder_button.clicked.connect(self.open_project_folder)
|
||||
self.delete_project_button.clicked.connect(self.delete_current_project)
|
||||
self.project_table.itemSelectionChanged.connect(self._on_project_selection_changed)
|
||||
self.original_table.cellClicked.connect(self._on_original_clicked)
|
||||
self.original_table.cellDoubleClicked.connect(self._on_original_double_clicked)
|
||||
@@ -695,18 +698,18 @@ class ImageStudioTab(QWidget):
|
||||
self.projects = []
|
||||
self._status(f"AI工场项目读取失败:{exc}", "danger")
|
||||
self._fill_project_table()
|
||||
if self.current_project is None and self.projects:
|
||||
self._select_project(self.projects[0].id)
|
||||
elif self.current_project is not None:
|
||||
active_ids = {int(project.id) for project in self.projects}
|
||||
if self.current_project is not None and int(self.current_project.id) in active_ids:
|
||||
self._select_project(self.current_project.id, quiet=True)
|
||||
elif self.projects:
|
||||
self._select_project(self.projects[0].id)
|
||||
else:
|
||||
self._refresh_project_summary()
|
||||
self._clear_current_project()
|
||||
|
||||
def _fill_project_table(self):
|
||||
self.project_table.setRowCount(len(self.projects))
|
||||
for row, project in enumerate(self.projects):
|
||||
values = [
|
||||
f"{project.account_alias} / {project.item_id}",
|
||||
project.account_name or project.account_alias,
|
||||
project.item_id,
|
||||
project.updated_at,
|
||||
@@ -718,38 +721,11 @@ class ImageStudioTab(QWidget):
|
||||
self.project_table.setRowHeight(row, 44)
|
||||
self.project_table.resizeColumnsToContents()
|
||||
|
||||
def open_project(self, checked=False):
|
||||
alias = str(self.account_combo.currentData() or "").strip()
|
||||
item_id = self.item_id_edit.text().strip()
|
||||
if not alias:
|
||||
self._message("账号未选择", "请先在④账号管理添加并选择账号。")
|
||||
return
|
||||
if not item_id:
|
||||
self._message("商品ID不能为空", "请输入要打开的蝦皮商品ID。")
|
||||
return
|
||||
account = accounts.get_account(alias, path=self.db_path, config=self.config)
|
||||
try:
|
||||
project = image_studio.create_or_get_project(
|
||||
account,
|
||||
item_id=item_id,
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._message("打开商品项目失败", str(exc))
|
||||
self._status(f"打开AI工场商品项目失败:{exc}", "danger")
|
||||
return
|
||||
self.current_project = project
|
||||
self.item_id_edit.setText(project.item_id)
|
||||
self._set_account_combo(project.account_alias)
|
||||
self.refresh_projects()
|
||||
self._select_project(project.id)
|
||||
self._status("AI工场商品项目已打开", "success")
|
||||
|
||||
def pull_main_images(self, checked=False):
|
||||
alias = str(self.account_combo.currentData() or "").strip()
|
||||
item_id = self.item_id_edit.text().strip()
|
||||
if not alias or not item_id:
|
||||
self._message("商品项目未打开", "请先选择账号和商品ID并打开商品项目。")
|
||||
self._message("信息未填写完整", "请先选择店铺并输入商品ID,再拉取蝦皮主图。")
|
||||
return
|
||||
worker = ImageStudioPullImagesWorker(
|
||||
alias,
|
||||
@@ -779,7 +755,7 @@ class ImageStudioTab(QWidget):
|
||||
|
||||
def open_project_folder(self, checked=False):
|
||||
if self.current_project is None:
|
||||
self._message("商品项目未打开", "请先打开一个AI工场商品项目。")
|
||||
self._message("未选择商品", "请先从商品列表选择商品,或拉取蝦皮主图。")
|
||||
return
|
||||
dirs = image_studio.default_project_image_dirs(self.current_project, self.config)
|
||||
try:
|
||||
@@ -791,6 +767,44 @@ class ImageStudioTab(QWidget):
|
||||
return
|
||||
self._status(f"已打开项目文件夹:{opened}", "success")
|
||||
|
||||
def delete_current_project(self, checked=False):
|
||||
project = self.current_project
|
||||
if project is None:
|
||||
self._message("未选择商品", "请先从商品列表选择要删除的项目。")
|
||||
return
|
||||
active_jobs = [
|
||||
job
|
||||
for job in self._list_project_jobs(project.id)
|
||||
if job.status in {"pending", "submitted", "running"}
|
||||
]
|
||||
if active_jobs:
|
||||
self._message(
|
||||
"暂不能删除项目",
|
||||
"该商品还有未完成的图片任务。请先等待任务完成、停止本轮或继续查询任务后再删除。",
|
||||
)
|
||||
return
|
||||
display_name = _project_display_name(project)
|
||||
if not self._confirm(
|
||||
"删除项目",
|
||||
f"确定从AI工场商品列表删除“{display_name}”吗?\n"
|
||||
"不会删除蝦皮商品,也不会删除本地图片。再次拉取同一商品会恢复原项目记录。",
|
||||
):
|
||||
return
|
||||
try:
|
||||
image_studio.soft_delete_project(
|
||||
project.id,
|
||||
reason="用户从AI工场商品列表删除",
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
message = diagnostics.redact_log_text(str(exc or "未知错误"))
|
||||
self._message("删除项目失败", f"无法从AI工场商品列表删除:{message}")
|
||||
self._status(f"删除AI工场项目失败:{message}", "danger")
|
||||
return
|
||||
self._clear_current_project()
|
||||
self.refresh_projects()
|
||||
self._status(f"已从AI工场商品列表删除:{display_name}", "success")
|
||||
|
||||
def _on_project_selection_changed(self):
|
||||
items = self.project_table.selectedItems()
|
||||
if not items:
|
||||
@@ -820,7 +834,7 @@ class ImageStudioTab(QWidget):
|
||||
self._sync_project_selection(project.id)
|
||||
self._refresh_project_summary()
|
||||
if not quiet:
|
||||
self._status(f"当前AI工场商品项目:{project.account_alias} / {project.item_id}", "muted")
|
||||
self._status(f"当前AI工场商品:{_project_display_name(project)}", "muted")
|
||||
|
||||
def _sync_project_selection(self, project_id):
|
||||
for row in range(self.project_table.rowCount()):
|
||||
@@ -1068,17 +1082,42 @@ class ImageStudioTab(QWidget):
|
||||
|
||||
def _refresh_project_summary(self):
|
||||
if self.current_project is None:
|
||||
self.current_project_label.setText("未打开商品项目")
|
||||
self.autosave_label.setText("选择账号并输入商品ID后打开")
|
||||
self.workspace_empty_label.setText("选择账号并输入商品ID,点击「打开商品项目」开始。")
|
||||
self.current_project_label.setText("未选择商品")
|
||||
self.current_project_label.setToolTip("")
|
||||
self.autosave_label.setText("选择店铺并输入商品ID后拉取")
|
||||
self.workspace_empty_label.setText("选择店铺并输入商品ID后,点击「拉取蝦皮主图」开始。")
|
||||
self._update_project_action_buttons()
|
||||
return
|
||||
alias = self.current_project.account_alias
|
||||
item_id = self.current_project.item_id
|
||||
self.current_project_label.setText(f"{alias} · {item_id}")
|
||||
self.current_project_label.setText(_project_display_name(self.current_project))
|
||||
self.current_project_label.setToolTip(f"账号别名:{self.current_project.account_alias}")
|
||||
self.autosave_label.setText("项目已自动保存")
|
||||
self.workspace_empty_label.setText(
|
||||
f"当前商品:{alias} / {item_id}。按顺序拉取蝦皮主图、选择源图、生成并拖入终选。"
|
||||
f"当前商品:{_project_display_name(self.current_project)}。按顺序拉取蝦皮主图、选择源图、生成并拖入终选。"
|
||||
)
|
||||
self._update_project_action_buttons()
|
||||
|
||||
def _clear_current_project(self):
|
||||
self.current_project = None
|
||||
self.assets = []
|
||||
self.jobs = []
|
||||
self.selections = []
|
||||
self.selected_source_asset_id = None
|
||||
self.item_id_edit.clear()
|
||||
self.prompt_edit.blockSignals(True)
|
||||
try:
|
||||
self.prompt_edit.clear()
|
||||
finally:
|
||||
self.prompt_edit.blockSignals(False)
|
||||
self._fill_original_table()
|
||||
self._fill_pool_table()
|
||||
self._refresh_selection_labels()
|
||||
self._refresh_source_label()
|
||||
self._refresh_project_summary()
|
||||
|
||||
def _update_project_action_buttons(self):
|
||||
enabled = self.current_project is not None and not self._operation_running
|
||||
self.open_folder_button.setEnabled(enabled)
|
||||
self.delete_project_button.setEnabled(enabled)
|
||||
|
||||
def _update_generation_action_text(self, *args):
|
||||
count = self.count_spin.value() if hasattr(self, "count_spin") else 0
|
||||
@@ -1217,7 +1256,7 @@ class ImageStudioTab(QWidget):
|
||||
|
||||
def start_generation(self, checked=False):
|
||||
if self.current_project is None:
|
||||
self._message("商品项目未打开", "请先打开一个AI工场商品项目。")
|
||||
self._message("未选择商品", "请先从商品列表选择商品,或拉取蝦皮主图。")
|
||||
return
|
||||
source = self._asset_by_id(self.selected_source_asset_id)
|
||||
if source is None:
|
||||
@@ -1260,7 +1299,7 @@ class ImageStudioTab(QWidget):
|
||||
|
||||
def resume_generation_jobs(self, checked=False):
|
||||
if self.current_project is None:
|
||||
self._message("商品项目未打开", "请先打开一个AI工场商品项目。")
|
||||
self._message("未选择商品", "请先从商品列表选择商品,或拉取蝦皮主图。")
|
||||
return
|
||||
resumable = image_studio.list_resumable_jobs(
|
||||
path=self.db_path,
|
||||
@@ -1289,7 +1328,7 @@ class ImageStudioTab(QWidget):
|
||||
|
||||
def export_selections(self, checked=False):
|
||||
if self.current_project is None:
|
||||
self._message("商品项目未打开", "请先打开一个AI工场商品项目。")
|
||||
self._message("未选择商品", "请先从商品列表选择商品,或拉取蝦皮主图。")
|
||||
return
|
||||
main_count = len(self._selection_asset_ids("main"))
|
||||
detail_count = len(self._selection_asset_ids("detail"))
|
||||
@@ -1430,9 +1469,8 @@ class ImageStudioTab(QWidget):
|
||||
self._running_worker = None
|
||||
|
||||
def _set_running(self, running):
|
||||
self.open_project_button.setEnabled(not running)
|
||||
self._operation_running = bool(running)
|
||||
self.pull_images_button.setEnabled(not running)
|
||||
self.open_folder_button.setEnabled(not running)
|
||||
self.project_table.setEnabled(not running)
|
||||
self.original_table.setEnabled(not running)
|
||||
self.pool_table.setEnabled(not running)
|
||||
@@ -1451,6 +1489,7 @@ class ImageStudioTab(QWidget):
|
||||
self.resume_button.setEnabled(not running)
|
||||
self.stop_button.setEnabled(running)
|
||||
self.export_button.setEnabled(not running)
|
||||
self._update_project_action_buttons()
|
||||
|
||||
def _set_account_combo(self, alias):
|
||||
index = self.account_combo.findData(alias)
|
||||
@@ -1586,6 +1625,13 @@ def _asset_badge(kind):
|
||||
}.get(str(kind or ""), str(kind or "图片"))
|
||||
|
||||
|
||||
def _project_display_name(project):
|
||||
account_name = str(getattr(project, "account_name", "") or "").strip()
|
||||
account_alias = str(getattr(project, "account_alias", "") or "").strip()
|
||||
item_id = str(getattr(project, "item_id", "") or "").strip()
|
||||
return f"{account_name or account_alias or '未命名店铺'} · {item_id or '未填写商品ID'}"
|
||||
|
||||
|
||||
def _asset_status_text(asset):
|
||||
status = str(getattr(asset, "status", "") or "")
|
||||
local_path = str(getattr(asset, "local_path", "") or "")
|
||||
|
||||
Reference in New Issue
Block a user