From 098dc667b3a63147098a734e8b35dd87e0a041cc Mon Sep 17 00:00:00 2001 From: chengma Date: Fri, 17 Jul 2026 11:00:02 +0800 Subject: [PATCH] feat(product-suite): refine history account filters --- app/gui/tabs/product_suite.py | 63 ++++++++++++++++++++----- app/image_studio.py | 41 ++++++++++++++++- docs/04-architecture.md | 1 + docs/api.md | 5 +- docs/routes.md | 6 +-- docs/tasks/T-649.md | 4 +- tests/test_image_studio.py | 24 ++++++++++ tests/test_product_suite_gui.py | 82 +++++++++++++++++++++++++++------ 8 files changed, 195 insertions(+), 31 deletions(-) diff --git a/app/gui/tabs/product_suite.py b/app/gui/tabs/product_suite.py index 79e655c..d491faf 100644 --- a/app/gui/tabs/product_suite.py +++ b/app/gui/tabs/product_suite.py @@ -1449,12 +1449,11 @@ class ProductSuiteGlobalHistoryDialog(QDialog): filters = QHBoxLayout() filters.setSpacing(8) filters.addWidget(QLabel("店铺")) - self.account_filter_edit = QLineEdit() - self.account_filter_edit.setObjectName("suiteGlobalHistoryAccountFilter") - self.account_filter_edit.setPlaceholderText("店铺或账号") - self.account_filter_edit.setClearButtonEnabled(True) - self.account_filter_edit.returnPressed.connect(self.refresh_history) - filters.addWidget(self.account_filter_edit, 1) + self.account_filter_combo = QComboBox() + self.account_filter_combo.setObjectName("suiteGlobalHistoryAccountFilter") + self.account_filter_combo.setToolTip("选择要查看历史生成记录的店铺") + self.account_filter_combo.currentIndexChanged.connect(self.refresh_history) + filters.addWidget(self.account_filter_combo, 1) filters.addWidget(QLabel("商品ID")) self.item_filter_edit = QLineEdit() self.item_filter_edit.setObjectName("suiteGlobalHistoryItemFilter") @@ -1531,6 +1530,11 @@ class ProductSuiteGlobalHistoryDialog(QDialog): def refresh_history(self, checked=False): scroll_value = self.scroll.verticalScrollBar().value() + try: + self._refresh_account_filter_options() + except Exception as exc: + self._set_error("历史店铺读取失败:%s" % _user_error(exc)) + return self._clear_history_content() self._offset = 0 self._has_more = False @@ -1558,7 +1562,7 @@ class ProductSuiteGlobalHistoryDialog(QDialog): else None ) rounds = image_studio.list_global_generation_rounds( - account_query=self.account_filter_edit.text(), + account_alias=self.account_filter_combo.currentData(), item_query=self.item_filter_edit.text(), project_id=project_id, limit=self.PAGE_SIZE, @@ -1582,6 +1586,45 @@ class ProductSuiteGlobalHistoryDialog(QDialog): self._update_summary() return True + def _refresh_account_filter_options(self): + selected_alias = self.account_filter_combo.currentData() + current_accounts = accounts.list_accounts(path=self.db_path) + history_accounts = image_studio.list_global_history_accounts(path=self.db_path) + + active_by_alias = {} + for account in current_accounts: + alias = str(getattr(account, "alias", "") or "").strip() + if alias: + active_by_alias[alias] = account + + previous = self.account_filter_combo.blockSignals(True) + try: + self.account_filter_combo.clear() + self.account_filter_combo.addItem("全部店铺", None) + for alias in sorted( + active_by_alias, + key=lambda value: (value.casefold(), value), + ): + account = active_by_alias[alias] + account_name = str(getattr(account, "account_name", "") or "").strip() + self.account_filter_combo.addItem( + "%s(%s)" % (account_name or alias, alias), + alias, + ) + for history_account in history_accounts: + alias = str(getattr(history_account, "account_alias", "") or "").strip() + if alias and alias not in active_by_alias: + self.account_filter_combo.addItem( + "历史店铺:%s(账号已删除)" % alias, + alias, + ) + selected_index = self.account_filter_combo.findData(selected_alias) + self.account_filter_combo.setCurrentIndex( + selected_index if selected_index >= 0 else 0 + ) + finally: + self.account_filter_combo.blockSignals(previous) + def _add_round(self, round_info): try: jobs = image_studio.list_generation_round_current_jobs( @@ -1938,11 +1981,8 @@ class ProductSuiteTab(QWidget): layout.setSpacing(8) self.history_button = QPushButton("历史生成") self.history_button.setObjectName("suiteHistoryButton") - self.history_button.setToolTip("查看当前商品的历史生成记录") + self.history_button.setToolTip("查看所有商品的历史生成记录") layout.addWidget(self.history_button) - self.open_folder_button = QPushButton("打开结果文件夹") - self.open_folder_button.setObjectName("suiteOpenFolderButton") - layout.addWidget(self.open_folder_button) self.add_images_button = QPushButton("添加图片") self.add_images_button.setObjectName("suiteAddImagesButton") self.add_images_button.setAccessibleName("添加商品原图") @@ -2292,7 +2332,6 @@ class ProductSuiteTab(QWidget): self.custom_category_edit.editingFinished.connect(self._finish_custom_category_edit) self.generate_button.clicked.connect(self.toggle_generation) self.history_button.clicked.connect(self.open_history_dialog) - self.open_folder_button.clicked.connect(self.open_project_folder) self.undo_button.clicked.connect(self.undo_delete) self.more_button.clicked.connect(self._show_more_menu) diff --git a/app/image_studio.py b/app/image_studio.py index 38ae8a6..25c19f3 100644 --- a/app/image_studio.py +++ b/app/image_studio.py @@ -151,6 +151,13 @@ class ImageStudioHistoryRound: is_legacy: bool +@dataclass(frozen=True) +class ImageStudioHistoryAccount: + """One account alias retained by active product-suite history.""" + + account_alias: str + + @dataclass(frozen=True) class ImageStudioSuccessfulGenerationHistorySummary: """Successful persisted output summary for one active product project.""" @@ -1379,8 +1386,36 @@ def get_successful_generation_history_summary(project_id, path=None, conn=None): ) +def list_global_history_accounts(path=None, conn=None): + """List account aliases retained by non-deleted product-suite history.""" + + sql = """ + SELECT projects.account_alias AS account_alias + FROM image_studio_projects AS projects + WHERE projects.deleted_at IS NULL + AND TRIM(COALESCE(projects.account_alias, '')) <> '' + AND EXISTS ( + SELECT 1 + FROM image_studio_jobs AS jobs + WHERE jobs.project_id = projects.id + ) + GROUP BY projects.account_alias + ORDER BY projects.account_alias COLLATE NOCASE ASC, + projects.account_alias ASC + """ + with _connection(conn, path) as database: + rows = database.execute(sql).fetchall() + return [ + ImageStudioHistoryAccount( + account_alias=str(row["account_alias"] or ""), + ) + for row in rows + ] + + def list_global_generation_rounds( *, + account_alias=None, account_query="", item_query="", project_id=None, @@ -1411,8 +1446,12 @@ def list_global_generation_rounds( params.append(int(project_id)) except (TypeError, ValueError) as exc: raise db.DbError("当前商品项目无效") from exc + account_alias_text = str(account_alias or "").strip() account_text = str(account_query or "").strip() - if account_text: + if account_alias_text: + clauses.append("projects.account_alias = ?") + params.append(account_alias_text) + if not account_alias_text and account_text: pattern = "%%%s%%" % account_text clauses.append( "(projects.account_alias LIKE ? OR COALESCE(projects.account_name, '') LIKE ?)" diff --git a/docs/04-architecture.md b/docs/04-architecture.md index c6da12a..e283371 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -447,6 +447,7 @@ data/images///__new. # AI 生成的新 - T-643 后 `image_studio_projects.current_generation_round_key` 是⑥主结果区的持久化当前轮次;每次常规「生成套图」创建独立 UUID `generation_round_key`,并为每条 `image_studio_jobs` 写入稳定的 `generation_slot_index`。内存 `run_token` 仍只用于线程和迟到信号隔离,不承担业务轮次语义。只有一轮中至少一条 job 成功时,才在同一 SQLite 事务内将该轮提升为当前轮;全失败或全取消保留原当前轮,部分成功则保留成功、失败槽位供后续重试。单张重试新建 job 但继承原轮次和槽位,当前展示按同轮同槽位最新 job、槽位顺序读取;所有尝试、错误、计费和输出资产均保留。旧 job 的轮次/槽位保持 NULL,查询层统一作为“旧版历史记录”,不以时间、文件名或数量猜测轮次;下一次成功的新格式整轮才建立当前轮。 - T-646 后⑥主结果区仍只显示项目的当前轮次;「历史生成」改为全局非模态只读窗口,默认跨所有未软删除商品项目按轮次创建时间倒序读取(同一时间以稳定 job ID 补序),首屏及每次翻页最多30轮。窗口只读取 SQLite 的轮次摘要、每槽最新有效 job 和受限尺寸缩略图,不扫描图片目录或一次加载原图;店铺/账号、商品 ID 与“仅当前商品”筛选都在查询层完成,当前商品快捷筛选默认关闭。正常 `generation_round_key` 一轮一行,单张重试只更新该槽位当前状态并计入重试数,不新增历史行;NULL 轮次继续作为“旧版历史记录”兼容,不猜测轮次边界。每行最多显示5张缩略图,余量显示 `+N`;文件缺失只显示中文占位,不删除 DB。双击缩略图从被点图片打开该轮所有可用图片的自适应原尺寸浏览;“导出本轮”在后台仅复制成功且本地存在的输出 asset 到用户选择目录下安全命名的新子目录,确定性追加序号避免覆盖,不移动/重命名/删除内部 asset。窗口不提供批量导出、删除、重试、设为当前轮或重新生成;关闭商品任务不关闭全局窗口,应用退出时协作停止导出并释放窗口资源,生成 worker 不受历史窗口影响。 - T-648 后,用户发起一轮常规「生成套图」前,服务层仅根据 SQLite 中当前项目每个稳定槽位的最新有效成功 job(NULL 旧版轮次仍按一轮兼容)汇总成功轮数、图片数和最近成功时间;不扫描生成目录。若摘要非空,GUI 先显示默认取消的「已有套图生成记录」三选一确认:查看历史仅打开同一全局历史窗口并临时勾选“仅当前商品”,不创建轮次或请求;继续生成新一轮才进入原有费用确认,并在最终确认后生成新的 UUID 轮次;取消或关闭不写 job、asset、轮次或计费。单张重试、恢复未完成任务及只有失败/取消记录的项目不经过该确认。 +- T-649 后,历史窗口的店铺筛选改为结构化下拉:首项固定为「全部店铺」,当前账号按「账号名(账号别名)」显示;未软删除历史项目中仍存在、但账号管理已删除的别名按「历史店铺:别名(账号已删除)」保留。下拉值始终是稳定的 `account_alias`,查询使用 `projects.account_alias = ?` 精确匹配;商品 ID 与“仅当前商品”可叠加,刷新时保留仍存在的选择。⑥顶部不再提供打开内部生成目录的按钮,用户导出只从历史记录的「导出本轮」进入外部目标目录;结果卡的单图文件操作保持不变。 提示词管理: diff --git a/docs/api.md b/docs/api.md index bf05506..b14bb56 100644 --- a/docs/api.md +++ b/docs/api.md @@ -368,6 +368,7 @@ render_prompt(template_text, task) -> str ```python # app/image_studio.py ImageStudioProject / ImageStudioAsset / ImageStudioJob / ImageStudioSelection +ImageStudioHistoryAccount ImageStudioGenerationRound / ImageStudioHistoryRound create_or_get_project(account_or_fields, item_id, ...) -> ImageStudioProject list_projects(path=None) -> list[ImageStudioProject] @@ -383,7 +384,8 @@ list_jobs(project_id, statuses=None, path=None) -> list[ImageStudioJob] list_resumable_jobs(project_id=None, include_failed_downloads=False, path=None) -> list[ImageStudioJob] list_generation_rounds(project_id, limit=None, offset=0, path=None) -> list[ImageStudioGenerationRound] get_successful_generation_history_summary(project_id, path=None) -> ImageStudioSuccessfulGenerationHistorySummary -list_global_generation_rounds(account_query="", item_query="", project_id=None, +list_global_history_accounts(path=None) -> list[ImageStudioHistoryAccount] +list_global_generation_rounds(account_alias=None, account_query="", item_query="", project_id=None, limit=None, offset=0, path=None) -> list[ImageStudioHistoryRound] list_generation_round_current_jobs(project_id, generation_round_key, path=None) -> list[ImageStudioJob] list_generation_round_attempts(project_id, generation_round_key, path=None) -> list[ImageStudioJob] @@ -424,6 +426,7 @@ export_generation_round(project_id, generation_round_key, parent_dir, path=None, - T-643 后 `image_studio_projects.current_generation_round_key` 记录项目主界面的当前生成轮次;`image_studio_jobs.generation_round_key` 与 `generation_slot_index` 记录整轮归属和稳定展示槽位。`promote_generation_round_if_success()` 只会在同项目、同轮已存在成功 job 时原子切换当前轮;`list_generation_rounds()` 返回轮次摘要,`list_generation_round_current_jobs()` 返回每个槽位最新尝试,`list_generation_round_attempts()` 返回完整尝试历史。旧 job 保持 NULL 并按“旧版历史记录”兼容,不猜测轮次边界。 - T-646 后 `list_global_generation_rounds()` 是⑥跨项目历史的唯一查询入口:仅返回未软删除项目的轮次摘要,按创建时间倒序并支持账号/店铺、商品 ID、项目 ID、`limit`/`offset` 筛选;正常轮次只统计每个稳定槽位的最新有效 job,完整尝试数仅用于计算重试数,NULL 轮次保留“旧版历史记录”语义。`ProductSuiteGlobalHistoryDialog` 组合该查询和 `list_generation_round_current_jobs()`,默认读取30轮,显示受限缩略图并可按轮预览;窗口不写业务数据,也不改变当前轮次。`ProductSuiteHistoryExportWorker` 在后台调用 `export_generation_round()`,只复制该轮成功且存在的输出 asset 到用户选择目录中新建的安全子目录;同名目录追加稳定序号,不覆盖外部文件,部分文件失败只汇总中文结果,源 asset 保持不变。 - T-648 后 `get_successful_generation_history_summary()` 是⑥常规新一轮生成前的只读判据:它只查询未软删除当前项目的最新有效成功 job,返回成功轮数、成功图片数和最近成功时间;正常轮次按稳定槽位取最新尝试,NULL 旧版轮次整体按一轮计数,只有失败/取消/进行中的 job 返回零摘要。该 API 不扫描文件目录、不创建 job/asset/轮次,也不改变当前轮;GUI 的“查看历史”仅调用全局历史窗口并临时启用当前商品筛选。 +- T-649 后 `list_global_history_accounts()` 只返回含 job 且未软删除项目的历史账号别名,供历史窗口补齐已删除账号的可选项。`list_global_generation_rounds(account_alias=...)` 以 `projects.account_alias = ?` 精确筛选;传入该参数时优先于旧兼容参数 `account_query`,后者仅保留给既有调用的模糊查询。当前账号显示文本来自账号管理,历史缺失账号只显示明确的历史标识,UI 不通过显示文本反查筛选值。 - 拉取蝦皮原主图只读:复用 `editor.open_product(..., bring_to_front=False)` 和 `editor.read_product_image_urls()`,不上传、不拖拽、不点击更新。 - 原图下载走 `image_studio_images` 的公网 URL、大小、Content-Type、重定向和 PIL 解码校验;只在用户单击时落盘。 - `remove_original_assets_if_unused()` 会先校验整批原图的项目归属、资产类型及 job/终选引用,再在单个事务中删除资产行并连续重排 `source_order`;任一图片不可删除时整批不变,本地源文件和蝦皮线上图片始终保留。 diff --git a/docs/routes.md b/docs/routes.md index c93b025..65bef17 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -184,7 +184,7 @@ ``` ┌ 套图任务1 │ 套图任务2 │ + ───────────────────────────────────┐ -│ [历史生成][打开结果文件夹][添加图片] 账号[▼] 商品ID[____][拉取主图] │ +│ [历史生成][添加图片] 账号[▼] 商品ID[____][拉取主图] │ ├ 左侧配置(滚动)────────────┬ 右侧生成结果 ─────────────────────┤ │ 商品原图 N/16 已选N张[全选][反选]│ 共N张·成功M张 │ │ 主图/参考图按宽度换行自然展开 │ │ @@ -197,7 +197,7 @@ ``` - 每个顶部任务标签持有独立账号、商品ID、设置、原图、当前 job 集合和 worker;任务可并行生成。切换任务不停止后台操作;关闭运行中任务先确认并协作式取消,线程引用保留到真正结束,避免 `QThread: Destroyed while thread is still running`。 -- 二级套图任务标签使用独立紧凑样式,不继承主模块 Tab 的大尺寸点击区。上下文栏左侧集中「历史生成 / 打开结果文件夹 / 添加图片」,右侧集中账号、商品 ID 和拉取入口;常见 11~13 位商品 ID 不得裁切,长账号可通过 tooltip 查看完整名称。 +- 二级套图任务标签使用独立紧凑样式,不继承主模块 Tab 的大尺寸点击区。上下文栏左侧集中「历史生成 / 添加图片」,右侧集中账号、商品 ID 和拉取入口;常见 11~13 位商品 ID 不得裁切,长账号可通过 tooltip 查看完整名称。内部生成目录不提供顶部直达入口,正式取图统一通过历史窗口的「导出本轮」选择外部目录。 - 项目仍以 `账号别名 + 商品ID` 唯一,复用 `image_studio_projects/assets/jobs`。`suite_settings_json` 保存平台、国家地区、语言、比例、逐图主图模式和分类数量;卖点文本继续使用 `draft_prompt`,用户停止输入约 500ms 后自动保存,切换任务、关闭任务或程序前同步补保存。未创建项目时只保留在当前任务内存,不因输入文字自动创建临时草稿。 - 「商品卖点与要求」关闭内部横向和纵向滚动条,输入框按完整换行内容自然增高;文本删除后可缩回最低高度,页面过长时统一由左侧 `suiteConfigScroll` 滚动。 - 商品原图最多16张。前6个槽位固定显示主图与参考1~5;列表关闭内部滚动条,按可用宽度换行并自然向下展开,由左侧配置区统一滚动。支持文件选择、外部拖入、剪贴板粘贴和列表内排序。历史失效远程图不占有效名额;第1张是主参考图。 @@ -207,7 +207,7 @@ - 平台、国家地区、语言和比例以四个带独立标签的同行下拉展示,选项只显示真实值;四项都写进每个 job 的完整提示词,比例还透传到 cmhub 生图请求,不是装饰字段。已有项目保存自己的完整设置;未绑定商品的新任务在重启后采用 `config.json` 的最近四项选择。生成仍走 `image_studio_generation.run_jobs()` 的 submit → poll → download 管线。 - 生成按钮按当前总数显示并在运行时切换为「停止生成」;确认停止后显示「正在停止...」,重复点击不再弹确认框。每轮生成用独立运行标识隔离旧信号,本轮全部 job 终态或线程结束时都会统一恢复按钮;最终 worker 信号缺失时由数据库终态看门狗兜底,不要求用户重启。停止会取消未开始任务,已提交任务停止本地等待并保留后续继续查询语义;客户端不承诺取消服务端任务或退回点数。T-643 后项目持久化当前生成轮次:常规新轮至少成功一张才替换主结果区,全部失败/取消保留上一当前轮;主结果按稳定槽位显示同轮最新 job,单张重试留在原槽位。旧版无轮次 job 临时显示为“旧版历史记录”,不按时间或图片数量猜测归属。成功图可预览、复制路径、打开目录、重新生成、移入项目废纸篓并撤销,失败卡显示脱敏中文摘要与重试入口。 - T-648 后,常规「生成套图」在原图、卖点和数量校验通过后、费用确认前,若 SQLite 记录显示当前商品已有成功套图,会出现「已有套图生成记录」确认框:用户可查看仅当前商品的全局历史、继续生成新一轮或取消,默认取消;查看历史和取消都不提交任务,继续仍须通过原费用确认后才创建新轮次。失败图片重试、恢复未完成任务和仅失败/已取消历史不出现该确认。 -- T-646 后「历史生成」打开全局非模态「套图历史生成记录」窗口,默认显示所有未删除商品项目最近创建的生成轮次,主结果区不切换。窗口支持店铺/账号、商品 ID 关键字和“仅当前商品”快捷筛选,但默认不限制当前任务;一行就是一次正常生成轮次,单张失败重试仍归入原行。行内固定显示时间、店铺/账号、商品 ID、成功/失败/停止/重试统计、最多5张缩略图及余量 `+N`,当前轮标记“当前”,NULL 轮次标记“旧版历史记录”,临时项目显示“临时草稿”。双击缩略图或整行从对应图片打开该轮所有可用图的自适应原尺寸浏览;“导出本轮”后台复制该轮成功且本地存在的图片到用户选择目录下的新安全子目录,不覆盖或修改内部图片。旧版记录、全失败轮和本地文件缺失项保留中文说明;不提供批量导出、删除、重试、切换当前轮或再次生成。重复点击复用同一窗口;关闭任务页不关闭全局窗口,应用退出时正常释放。 +- T-646 后「历史生成」打开全局非模态「套图历史生成记录」窗口,默认显示所有未删除商品项目最近创建的生成轮次,主结果区不切换。T-649 将店铺筛选固定为首项「全部店铺」的下拉:当前账号显示「账号名(账号别名)」,已删除但仍有历史项目的账号显示「历史店铺:别名(账号已删除)」,选择值使用 `account_alias` 精确查询;商品 ID 关键字和“仅当前商品”可与其叠加,默认不限制当前任务。每一行就是一次正常生成轮次,单张失败重试仍归入原行。行内固定显示时间、店铺/账号、商品 ID、成功/失败/停止/重试统计、最多5张缩略图及余量 `+N`,当前轮标记“当前”,NULL 轮次标记“旧版历史记录”,临时项目显示“临时草稿”。双击缩略图或整行从对应图片打开该轮所有可用图的自适应原尺寸浏览;“导出本轮”后台复制该轮成功且本地存在的图片到用户选择目录下的新安全子目录,不覆盖或修改内部图片。旧版记录、全失败轮和本地文件缺失项保留中文说明;不提供批量导出、删除、重试、切换当前轮或再次生成。重复点击复用同一窗口;关闭任务页不关闭全局窗口,应用退出时正常释放。 - AI帮写和生图按任务独立运行。AI帮写只使用⑤设置的「图片理解别名」调用图片理解能力,不走②标题生成;按商品原图 `source_order` 取1至8张已下载的本地图片,在一次请求中作为同商品的多角度/细节/包装/场景证据集联合理解,超过8张时状态提示只使用前8张,原图勾选不改变输入图片。返回一份可直接编辑的商品级卖点与套图画面要求,按商品概述、可确认卖点、人群与场景、套图画面要求、待确认或避免编造的信息组织,不按图1、图2逐图说明;图片有可见差异时明确为待确认项。单图超过10MiB、总计超过32MiB、没有可用本地图、别名未配置或服务异常时不改现有卖点;图片理解读超时或网络中断提示“结果未确认,请先查看点数余额或稍后重试”,不自动重发。成功状态显示理解图片张数、扣点和余额;AI帮写期间若用户改过卖点,返回后必须确认才覆盖;全部用户可见错误隐藏图片路径、URL、接口路径、base64、完整提示词和敏感信息。 - ⑥只管理本地图片资产,不自动上传或修改蝦皮;③线上更新边界不受影响。旧 `ImageStudioTab` 留作代码兼容但不再作为主窗口入口。 diff --git a/docs/tasks/T-649.md b/docs/tasks/T-649.md index 7116965..7b150dc 100644 --- a/docs/tasks/T-649.md +++ b/docs/tasks/T-649.md @@ -3,7 +3,7 @@ id: T-649 title: 商品套图历史店铺下拉筛选和结果目录入口精简 phase: 7 deps: [T-646] -status: TODO +status: DONE created: 2026-07-17 --- @@ -85,3 +85,5 @@ git diff --check ## 执行记录 - 2026-07-17:根据全局历史筛选和结果目录入口精简的产品讨论创建任务。待实现。 +- 2026-07-17:历史窗口店铺筛选改为以 `account_alias` 为值的下拉框,保留全部店铺、当前账号和已删除账号的历史别名;查询层新增精确别名参数并保留旧文本参数兼容。移除⑥顶部「打开结果文件夹」,历史按轮导出和结果卡单图文件操作保持可用;同步更新架构、接口、路由说明及回归测试。 +- 验证通过:`py -3.10 -m unittest tests.test_image_studio tests.test_product_suite_gui`(74 项)、`py -3.10 -m unittest discover -s tests`(569 项)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check`。 diff --git a/tests/test_image_studio.py b/tests/test_image_studio.py index 964d169..69b2c37 100644 --- a/tests/test_image_studio.py +++ b/tests/test_image_studio.py @@ -1208,6 +1208,30 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase): ) ], ) + self.assertEqual( + ["main-shop", "second-shop"], + [ + account.account_alias + for account in image_studio.list_global_history_accounts(path=db_path) + ], + ) + self.assertEqual( + [first_project.id, first_project.id], + [ + round_.project_id + for round_ in image_studio.list_global_generation_rounds( + account_alias="main-shop", + path=db_path, + ) + ], + ) + self.assertEqual( + [], + image_studio.list_global_generation_rounds( + account_alias="main", + path=db_path, + ), + ) self.assertEqual( [second_project.id], [ diff --git a/tests/test_product_suite_gui.py b/tests/test_product_suite_gui.py index 65a1db0..951f61b 100644 --- a/tests/test_product_suite_gui.py +++ b/tests/test_product_suite_gui.py @@ -185,17 +185,12 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): tab.context_bar_layout.indexOf(tab.history_button), tab.context_bar_layout.indexOf(tab.account_combo), ) - self.assertLess( - tab.context_bar_layout.indexOf(tab.open_folder_button), - tab.context_bar_layout.indexOf(tab.account_combo), - ) self.assertLess( tab.context_bar_layout.indexOf(tab.add_images_button), tab.context_bar_layout.indexOf(tab.account_combo), ) self.assertEqual(-1, tab.results_toolbar_layout.indexOf(tab.history_button)) - self.assertEqual(-1, tab.results_toolbar_layout.indexOf(tab.open_folder_button)) - self.assertEqual("打开结果文件夹", tab.open_folder_button.text()) + self.assertFalse(hasattr(tab, "open_folder_button")) self.assertEqual("合计 5 张", tab.category_total_label.text()) self.assertEqual("生成套图(5)", tab.generate_button.text()) self.assertLess( @@ -218,6 +213,7 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): ) self.assertNotIn("详情图", visible_text) self.assertNotIn("AI工场", visible_text) + self.assertNotIn("打开结果文件夹", visible_text) self.assertIn("白底图", visible_text) self.assertIn("场景图", visible_text) self.assertIn("卖点图", visible_text) @@ -1986,18 +1982,40 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): config = self._config(temp_dir) project, sources = self._create_project_with_assets(temp_dir, config, 1) source = sources[0] + other_account = accounts.create_account( + "副店", + "other-shop", + debug_port=9223, + config=config, + ) + accounts.create_account( + "空店", + "empty-shop", + debug_port=9224, + config=config, + ) other_project = image_studio.create_or_get_project( - account_alias="other-shop", - account_name="副店", - account_slug="other-shop", + other_account, item_id="51100639511", path=config["db_path"], ) + former_project = image_studio.create_or_get_project( + account_alias="former-shop", + account_name="旧店", + account_slug="former-shop", + item_id="51100639512", + path=config["db_path"], + ) other_source = image_studio.add_asset( other_project.id, image_studio.ASSET_KIND_ORIGINAL, path=config["db_path"], ) + former_source = image_studio.add_asset( + former_project.id, + image_studio.ASSET_KIND_ORIGINAL, + path=config["db_path"], + ) for index in range(7): image_path = os.path.join(temp_dir, "global-%d.png" % index) self._write_image(image_path) @@ -2021,6 +2039,17 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): local_path=other_path, job_type="卖点图", ) + former_path = os.path.join(temp_dir, "former-global.png") + self._write_image(former_path) + self._create_history_job( + former_project, + former_source, + config["db_path"], + round_key="former-round", + slot_index=0, + local_path=former_path, + job_type="白底图", + ) image_studio.set_current_generation_round( project.id, "main-round", @@ -2034,7 +2063,28 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): dialog.show() self.app.processEvents() - self.assertEqual(2, dialog._round_count) + self.assertEqual(3, dialog._round_count) + self.assertEqual("全部店铺", dialog.account_filter_combo.itemText(0)) + main_account_index = dialog.account_filter_combo.findData("alias-a") + other_account_index = dialog.account_filter_combo.findData("other-shop") + empty_account_index = dialog.account_filter_combo.findData("empty-shop") + former_account_index = dialog.account_filter_combo.findData("former-shop") + self.assertGreaterEqual(main_account_index, 0) + self.assertGreaterEqual(other_account_index, 0) + self.assertGreaterEqual(empty_account_index, 0) + self.assertGreaterEqual(former_account_index, 0) + self.assertEqual( + "主店(alias-a)", + dialog.account_filter_combo.itemText(main_account_index), + ) + self.assertEqual( + "副店(other-shop)", + dialog.account_filter_combo.itemText(other_account_index), + ) + self.assertEqual( + "历史店铺:former-shop(账号已删除)", + dialog.account_filter_combo.itemText(former_account_index), + ) rows = dialog.findChildren(SuiteGlobalHistoryRoundRow) main_row = next( row for row in rows if row.round_info.project_id == project.id @@ -2057,8 +2107,7 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): dialog._preview_round(main_row, 1) preview.assert_called_once_with() - dialog.account_filter_edit.setText("副店") - dialog.refresh_history() + dialog.account_filter_combo.setCurrentIndex(other_account_index) self.app.processEvents() self.assertEqual(1, dialog._round_count) self.assertEqual( @@ -2066,7 +2115,14 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase): [row.round_info.project_id for row in dialog._history_rows], ) - dialog.account_filter_edit.clear() + dialog.item_filter_edit.setText("51100639511") + dialog.refresh_history() + self.app.processEvents() + self.assertEqual(1, dialog._round_count) + self.assertEqual("other-shop", dialog.account_filter_combo.currentData()) + + dialog.item_filter_edit.clear() + dialog.account_filter_combo.setCurrentIndex(0) dialog.current_project_checkbox.setChecked(True) self.app.processEvents() self.assertEqual(1, dialog._round_count)