ui: 调整设置页AI模型布局

This commit is contained in:
chengma
2026-06-29 11:45:10 +08:00
parent 3a79e34eab
commit 94fdae859d
6 changed files with 149 additions and 98 deletions
+122 -76
View File
@@ -19,6 +19,7 @@ try:
QDialogButtonBox, QDialogButtonBox,
QFileDialog, QFileDialog,
QFormLayout, QFormLayout,
QGridLayout,
QHBoxLayout, QHBoxLayout,
QHeaderView, QHeaderView,
QInputDialog, QInputDialog,
@@ -2857,18 +2858,6 @@ if QT_IMPORT_ERROR is None:
self.add_model_button = QPushButton("新增") self.add_model_button = QPushButton("新增")
self.delete_model_button = QPushButton("删除") self.delete_model_button = QPushButton("删除")
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(0, 0, 12, 0)
left_layout.addWidget(QLabel("AI 模型"))
left_layout.addWidget(self.model_combo)
left_toolbar = QHBoxLayout()
left_toolbar.addWidget(self.add_model_button)
left_toolbar.addWidget(self.delete_model_button)
left_toolbar.addStretch(1)
left_layout.addLayout(left_toolbar)
left_layout.addStretch(1)
self.enabled_checkbox = QCheckBox("启用") self.enabled_checkbox = QCheckBox("启用")
self.name_edit = QLineEdit() self.name_edit = QLineEdit()
self.name_edit.setObjectName("modelNameEdit") self.name_edit.setObjectName("modelNameEdit")
@@ -2956,88 +2945,104 @@ if QT_IMPORT_ERROR is None:
self.max_parallel_accounts_spin.setObjectName("maxParallelAccountsSpin") self.max_parallel_accounts_spin.setObjectName("maxParallelAccountsSpin")
self.max_parallel_accounts_spin.setRange(1, 16) self.max_parallel_accounts_spin.setRange(1, 16)
form = QFormLayout() model_picker_layout = QHBoxLayout()
form.addRow("", self.enabled_checkbox) model_picker_layout.addWidget(self.model_combo, 1)
form.addRow("服务商名", self.name_edit) model_picker_layout.addWidget(self.add_model_button)
form.addRow("类别", self.category_combo) model_picker_layout.addWidget(self.delete_model_button)
form.addRow("api_type", self.api_type_combo)
form.addRow("模型ID", self.model_id_edit)
form.addRow("网址", self.url_edit)
form.addRow("密钥", self.api_key_edit)
form.addRow("连接超时(秒)", self.connect_timeout_spin)
action_layout = QHBoxLayout() action_layout = QHBoxLayout()
action_layout.addWidget(self.save_model_button) action_layout.addWidget(self.save_model_button)
action_layout.addWidget(self.test_connection_button) action_layout.addWidget(self.test_connection_button)
action_layout.addStretch(1) action_layout.addStretch(1)
ai_form = QFormLayout() form = self._two_column_form(
ai_form.addRow("标题大模型", self.default_text_model_combo) [
ai_form.addRow("图片大模型", self.default_image_model_combo) ("状态", self.enabled_checkbox),
ai_form.addRow("标题并发数", self.title_concurrency_spin) ("服务商名", self.name_edit),
ai_form.addRow("图片并发数", self.image_concurrency_spin) ("类别", self.category_combo),
ai_form.addRow("失败重试次数", self.retry_spin) ("api_type", self.api_type_combo),
ai_form.addRow("分辨率", self.resolution_combo) ("模型ID", self.model_id_edit),
ai_form.addRow("返回超时", self.response_timeout_label) ("连接超时(秒)", self.connect_timeout_spin),
ai_form.addRow("jpg质量", self.jpg_quality_spin) ("网址", self.url_edit, True),
("密钥", self.api_key_edit, True),
]
)
ai_form = self._two_column_form(
[
("标题大模型", self.default_text_model_combo),
("图片大模型", self.default_image_model_combo),
("标题并发数", self.title_concurrency_spin),
("图片并发数", self.image_concurrency_spin),
("失败重试次数", self.retry_spin),
("分辨率", self.resolution_combo),
("返回超时", self.response_timeout_label),
("jpg质量", self.jpg_quality_spin),
]
)
port_range_layout = QHBoxLayout() port_range_layout = QHBoxLayout()
port_range_layout.setContentsMargins(0, 0, 0, 0)
port_range_layout.addWidget(self.debug_port_start_spin) port_range_layout.addWidget(self.debug_port_start_spin)
port_range_layout.addWidget(QLabel("到")) port_range_layout.addWidget(QLabel("到"))
port_range_layout.addWidget(self.debug_port_end_spin) port_range_layout.addWidget(self.debug_port_end_spin)
port_range_widget = QWidget()
port_range_widget.setLayout(port_range_layout)
path_form = QFormLayout() path_form = self._two_column_form(
path_form.addRow("Chrome路径", self.chrome_path_edit) [
path_form.addRow("账号数据根目录", self.user_data_root_edit) ("Chrome路径", self.chrome_path_edit, True),
path_form.addRow("图片目录", self.image_dir_edit) ("账号数据根目录", self.user_data_root_edit),
path_form.addRow("DB路径", self.db_path_edit) ("图片目录", self.image_dir_edit),
path_form.addRow("默认调试端口", self.default_debug_port_spin) ("DB路径", self.db_path_edit),
path_form.addRow("调试端口范围", port_range_layout) ("默认调试端口", self.default_debug_port_spin),
path_form.addRow("CDP就绪超时(秒)", self.cdp_ready_timeout_spin) ("调试端口范围", port_range_widget),
("CDP就绪超时(秒)", self.cdp_ready_timeout_spin),
]
)
update_form = QFormLayout() update_form = self._two_column_form(
update_form.addRow("测试商品ID", self.test_item_id_edit) [
update_form.addRow("", self.allow_real_submit_checkbox) ("测试商品ID", self.test_item_id_edit),
update_form.addRow("", self.allow_cover_update_checkbox) ("单次最大更新条数", self.max_items_per_run_spin),
update_form.addRow("单次最大更新条数", self.max_items_per_run_spin) ("", self.allow_real_submit_checkbox),
update_form.addRow("", self.close_success_tab_checkbox) ("", self.allow_cover_update_checkbox),
update_form.addRow("", self.dry_run_checkbox) ("", self.close_success_tab_checkbox),
update_form.addRow("", self.parallel_accounts_checkbox) ("", self.dry_run_checkbox),
update_form.addRow("最大并行账号数", self.max_parallel_accounts_spin) ("", self.parallel_accounts_checkbox),
("最大并行账号数", self.max_parallel_accounts_spin),
]
)
right_panel = QWidget() panel = QWidget()
right_layout = QVBoxLayout(right_panel) panel_layout = QVBoxLayout(panel)
right_layout.setContentsMargins(12, 0, 0, 0) panel_layout.setContentsMargins(0, 0, 0, 0)
right_layout.addWidget(QLabel("模型详情")) panel_layout.addWidget(QLabel("AI 模型"))
right_layout.addLayout(form) panel_layout.addLayout(model_picker_layout)
right_layout.addLayout(action_layout) panel_layout.addSpacing(14)
right_layout.addWidget(self.test_result_label) panel_layout.addWidget(QLabel("模型详情"))
right_layout.addSpacing(18) panel_layout.addLayout(form)
right_layout.addWidget(QLabel("角色与生成参数")) panel_layout.addLayout(action_layout)
right_layout.addLayout(ai_form) panel_layout.addWidget(self.test_result_label)
right_layout.addSpacing(18) panel_layout.addSpacing(18)
right_layout.addWidget(QLabel("路径与端口")) panel_layout.addWidget(QLabel("角色与生成参数"))
right_layout.addLayout(path_form) panel_layout.addLayout(ai_form)
right_layout.addSpacing(18) panel_layout.addSpacing(18)
right_layout.addWidget(QLabel("Shopee 更新安全")) panel_layout.addWidget(QLabel("路径与端口"))
right_layout.addLayout(update_form) panel_layout.addLayout(path_form)
right_layout.addWidget(self.save_config_button) panel_layout.addSpacing(18)
right_layout.addStretch(1) panel_layout.addWidget(QLabel("Shopee 更新安全"))
panel_layout.addLayout(update_form)
panel_layout.addWidget(self.save_config_button)
panel_layout.addStretch(1)
right_scroll = QScrollArea() scroll = QScrollArea()
right_scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
right_scroll.setWidget(right_panel) scroll.setWidget(panel)
self.splitter = QSplitter(Qt.Horizontal)
self.splitter.addWidget(left_panel)
self.splitter.addWidget(right_scroll)
self.splitter.setStretchFactor(0, 1)
self.splitter.setStretchFactor(1, 3)
self.splitter.setSizes([280, 860])
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(18, 18, 18, 18) layout.setContentsMargins(18, 18, 18, 18)
layout.addWidget(self.splitter, 1) layout.addWidget(scroll, 1)
self.model_combo.currentIndexChanged.connect(self.load_selected_model) self.model_combo.currentIndexChanged.connect(self.load_selected_model)
self.add_model_button.clicked.connect(self.add_model) self.add_model_button.clicked.connect(self.add_model)
@@ -3052,6 +3057,47 @@ if QT_IMPORT_ERROR is None:
self.refresh_models() self.refresh_models()
self._populate_app_settings() self._populate_app_settings()
def _two_column_form(self, fields):
layout = QGridLayout()
layout.setHorizontalSpacing(18)
layout.setVerticalSpacing(8)
layout.setColumnStretch(1, 1)
layout.setColumnStretch(3, 1)
row = 0
column_pair = 0
for field in fields:
label = field[0]
widget = field[1]
full_width = len(field) > 2 and bool(field[2])
if full_width and column_pair:
row += 1
column_pair = 0
column = column_pair * 2
self._add_form_field(layout, row, column, label, widget, full_width)
if full_width:
row += 1
column_pair = 0
elif column_pair == 0:
column_pair = 1
else:
row += 1
column_pair = 0
return layout
def _add_form_field(self, layout, row, column, label, widget, full_width):
if full_width:
if label:
layout.addWidget(QLabel(label), row, 0)
layout.addWidget(widget, row, 1, 1, 3)
else:
layout.addWidget(widget, row, 0, 1, 4)
return
if label:
layout.addWidget(QLabel(label), row, column)
layout.addWidget(widget, row, column + 1)
else:
layout.addWidget(widget, row, column, 1, 2)
def _set_status(self, message): def _set_status(self, message):
if self.status_callback is not None: if self.status_callback is not None:
self.status_callback(message) self.status_callback(message)
+1 -1
View File
@@ -344,7 +344,7 @@ TAB_STYLE: str # 顶层 Tab 栏防误点样式:
⑤ 设置当前要点(T-501): ⑤ 设置当前要点(T-501):
- `SettingsTab` 左侧为 AI 模型下拉与新增/删除按钮,右侧为模型详情表单。 - `SettingsTab` 使用单列滚动布局:AI 模型下拉与新增/删除按钮位于“模型详情”上方;详情与设置表单尽量两项一行,长字段独占整行。
- 详情字段:启用、服务商名、类别(text/image)、api_type、模型 ID、网址、密钥、连接超时。 - 详情字段:启用、服务商名、类别(text/image)、api_type、模型 ID、网址、密钥、连接超时。
- 模型数据读写复用 `appconfig.list_ai_models(..., reveal_api_key=True)`、`add_ai_model()`、`update_ai_model()`、`delete_ai_model()`;保存时保留现有 `extra_body` 与 `timeout_seconds`。 - 模型数据读写复用 `appconfig.list_ai_models(..., reveal_api_key=True)`、`add_ai_model()`、`update_ai_model()`、`delete_ai_model()`;保存时保留现有 `extra_body` 与 `timeout_seconds`。
- 密钥字段使用 `QLineEdit.Password` 打码显示;首次保存/变更非空 Key 前弹窗提示“本地明文保存”;明文只写入已 gitignore 的 `config/ai_models.json`,不得进入日志/导出。 - 密钥字段使用 `QLineEdit.Password` 打码显示;首次保存/变更非空 Key 前弹窗提示“本地明文保存”;明文只写入已 gitignore 的 `config/ai_models.json`,不得进入日志/导出。
+1 -1
View File
@@ -8,7 +8,7 @@
- 日期:2026-06-29 - 日期:2026-06-29
- 阶段:V0 单账号 CDP 流程已验证;V1 已完成 T-000 正式代码包结构、T-001 `app/editor.py` 模块化、T-002 `app/appconfig.py` 应用配置、T-003 SQLite 持久化地基、T-004 本地数据忽略规则、T-005 AI 模型清单后端、T-006 单元测试基座、T-101 账号 user-data-dir 工具、T-102 Chrome 启动器、T-103 登录保活与检测、T-104 PySide6 主窗口骨架、T-104b PySide6 worker 基座、T-105 Tab④ 账号管理、T-106 账号快捷方式、T-201 Excel 导入入库、T-202 Tab① 任务列表与导入按钮、T-202b Tab① 导入汇总栏、T-203 采集旧标题旧封面、T-204 回写旧字段到原 Excel、T-204b 采集完成自动回写、T-205 首次未配账号/Chrome 未启动/未登录引导保护、T-205b 采集后关闭自动新建商品页 tab、T-301 AI 生成接口、T-302 Tab② AI 生成布局与任务列表、T-302p 提示词管理、T-303 Tab② 开始生成/停止/进度、T-401 Tab③ 更新列表筛选与开始更新确认、T-402 Tab③ 确认后串行更新、T-403 Tab③ 结果回写与结束汇总、T-501 Tab⑤ AI 模型管理 UI、T-501b Tab⑤ 角色与生成参数、T-501c Tab⑤ Shopee 更新安全开关、T-502 满 9 张封面删除再上传、T-503 敏感信息本地明文保存提示与日志脱敏、T-504 多账号并行/dry-run/运行日志。 - 阶段:V0 单账号 CDP 流程已验证;V1 已完成 T-000 正式代码包结构、T-001 `app/editor.py` 模块化、T-002 `app/appconfig.py` 应用配置、T-003 SQLite 持久化地基、T-004 本地数据忽略规则、T-005 AI 模型清单后端、T-006 单元测试基座、T-101 账号 user-data-dir 工具、T-102 Chrome 启动器、T-103 登录保活与检测、T-104 PySide6 主窗口骨架、T-104b PySide6 worker 基座、T-105 Tab④ 账号管理、T-106 账号快捷方式、T-201 Excel 导入入库、T-202 Tab① 任务列表与导入按钮、T-202b Tab① 导入汇总栏、T-203 采集旧标题旧封面、T-204 回写旧字段到原 Excel、T-204b 采集完成自动回写、T-205 首次未配账号/Chrome 未启动/未登录引导保护、T-205b 采集后关闭自动新建商品页 tab、T-301 AI 生成接口、T-302 Tab② AI 生成布局与任务列表、T-302p 提示词管理、T-303 Tab② 开始生成/停止/进度、T-401 Tab③ 更新列表筛选与开始更新确认、T-402 Tab③ 确认后串行更新、T-403 Tab③ 结果回写与结束汇总、T-501 Tab⑤ AI 模型管理 UI、T-501b Tab⑤ 角色与生成参数、T-501c Tab⑤ Shopee 更新安全开关、T-502 满 9 张封面删除再上传、T-503 敏感信息本地明文保存提示与日志脱敏、T-504 多账号并行/dry-run/运行日志。
- 技术栈:Python 3.10+,自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `config.json` + openpyxl + AI(`config/ai_models.json` 通用 HTTP,chat JSON / images_edits),GUI PySide6 5 Tab(已定)。 - 技术栈:Python 3.10+,自研 CDP(websocket-client + requests),SQLite(sqlite3)+ `config.json` + openpyxl + AI(`config/ai_models.json` 通用 HTTP,chat JSON / images_edits),GUI PySide6 5 Tab(已定)。
- 生产代码:已建立 `app/` 包 + 根入口 `main.py`;`app/cdp.py` 为已验证 CDP 底座,已区分 `CDP.close()` 断开 WebSocket 与 `close_tab()` 关闭浏览器 target;`app/editor.py` 已封装登录状态检测、标题/封面/采集/更新按钮能力,并在采集结束后只关闭本轮自动新建的商品编辑页 tab、保留用户已有 tab,更新提交成功且设置开启时可关闭本轮自动新建商品页;`replace_cover()` 已实现满 9 张时先校验本地旧封面备份,再点第一张删除、可见确认框、上传新图并拖到第一位的代码路径;`app/appconfig.py` 已实现 `config.json` 默认值/读写/更新、AI 参数、端口读取、Shopee 更新安全与执行模式默认值,`config/ai_models.json` 模型清单 CRUD/过滤/打码/测试连接,以及 `mask_secret()`、`sanitize_for_log()`、`redact_secrets()` 敏感信息脱敏工具;`app/ai.py` 已实现 `gen_title()`/`gen_cover()`/`generate_batch()`,按默认文本/图片模型发起通用 HTTP 调用,支持重试、错误脱敏、图片 URL/base64 解析、resolution resize、jpg_quality 保存、先并发标题再并发封面、逐条 `set_generated`、失败 `mark_failed` 与停止取消未开始项;`app/prompts.py` 已实现标题提示词读写、封面模板 CRUD 与变量替换;`app/db.py` 已实现 SQLite schema、连接 PRAGMA、批次/账号/任务与阶段写库函数,以及 `run_logs/run_log_events` 运行日志函数;`app/excel.py` 已实现多 Excel 输入列解析、整文件列校验、脏行统计跳过、导入批次与任务入库、别名匹配统计、旧标题/旧封面路径回写原 Excel、更新结果回写原 Excel 与另存副本;`app/config.py` 已实现账号 slug 与 user-data-dir 创建;`app/accounts.py` 已实现账号 CRUD 服务、端口默认分配、启动登录、检测登录、生成快捷方式;`app/chrome.py` 已实现 Chrome 参数拼装、启动、CDP 端口探测、PowerShell `.lnk` 快捷方式生成;`app/gui.py` 已实现 PySide6 `MainWindow`、五 Tab、顶部 Tab 栏防误点样式、① 导入采集的 Excel 导入按钮/导入汇总栏/QTableView 任务列表/未匹配筛选与略过标记/采集旧标题旧封面 worker/采集前账号就绪预检与④引导/采集完成自动回写/旧数据回写重试按钮与 worker、② AI生成左右布局/标题与封面提示词管理/批次店铺状态筛选/任务列表/变量预览/开始生成/停止/进度/双击新旧封面预览与 `GenerateWorker`、③ 更新shopee筛选栏/任务列表/更新安全开关拦截/开始更新确认弹窗/确认后 `ApplyWorker` dry-run 预览或真实更新/账号就绪和端口冲突预检/按账号并行可选/逐条 `set_applied`/运行日志/自动回写结果到 Excel/结束汇总弹窗/手动回写重试按钮、④ 账号管理表格/弹窗/按钮/快捷方式与状态栏、密码明文保存提示、⑤ 设置 AI 模型 master-detail 管理、API Key 明文保存提示、`AIModelTestWorker` 后台测试连接、角色/生成参数/路径/端口配置、Shopee 更新安全和 dry-run/多账号并行设置并持久化 `config.json`;`app/workers.py` 已实现 `BaseWorker`、通用 signals、取消标记和 `QThread` 启动包装。 - 生产代码:已建立 `app/` 包 + 根入口 `main.py`;`app/cdp.py` 为已验证 CDP 底座,已区分 `CDP.close()` 断开 WebSocket 与 `close_tab()` 关闭浏览器 target;`app/editor.py` 已封装登录状态检测、标题/封面/采集/更新按钮能力,并在采集结束后只关闭本轮自动新建的商品编辑页 tab、保留用户已有 tab,更新提交成功且设置开启时可关闭本轮自动新建商品页;`replace_cover()` 已实现满 9 张时先校验本地旧封面备份,再点第一张删除、可见确认框、上传新图并拖到第一位的代码路径;`app/appconfig.py` 已实现 `config.json` 默认值/读写/更新、AI 参数、端口读取、Shopee 更新安全与执行模式默认值,`config/ai_models.json` 模型清单 CRUD/过滤/打码/测试连接,以及 `mask_secret()`、`sanitize_for_log()`、`redact_secrets()` 敏感信息脱敏工具;`app/ai.py` 已实现 `gen_title()`/`gen_cover()`/`generate_batch()`,按默认文本/图片模型发起通用 HTTP 调用,支持重试、错误脱敏、图片 URL/base64 解析、resolution resize、jpg_quality 保存、先并发标题再并发封面、逐条 `set_generated`、失败 `mark_failed` 与停止取消未开始项;`app/prompts.py` 已实现标题提示词读写、封面模板 CRUD 与变量替换;`app/db.py` 已实现 SQLite schema、连接 PRAGMA、批次/账号/任务与阶段写库函数,以及 `run_logs/run_log_events` 运行日志函数;`app/excel.py` 已实现多 Excel 输入列解析、整文件列校验、脏行统计跳过、导入批次与任务入库、别名匹配统计、旧标题/旧封面路径回写原 Excel、更新结果回写原 Excel 与另存副本;`app/config.py` 已实现账号 slug 与 user-data-dir 创建;`app/accounts.py` 已实现账号 CRUD 服务、端口默认分配、启动登录、检测登录、生成快捷方式;`app/chrome.py` 已实现 Chrome 参数拼装、启动、CDP 端口探测、PowerShell `.lnk` 快捷方式生成;`app/gui.py` 已实现 PySide6 `MainWindow`、五 Tab、顶部 Tab 栏防误点样式、① 导入采集的 Excel 导入按钮/导入汇总栏/QTableView 任务列表/未匹配筛选与略过标记/采集旧标题旧封面 worker/采集前账号就绪预检与④引导/采集完成自动回写/旧数据回写重试按钮与 worker、② AI生成左右布局/标题与封面提示词管理/批次店铺状态筛选/任务列表/变量预览/开始生成/停止/进度/双击新旧封面预览与 `GenerateWorker`、③ 更新shopee筛选栏/任务列表/更新安全开关拦截/开始更新确认弹窗/确认后 `ApplyWorker` dry-run 预览或真实更新/账号就绪和端口冲突预检/按账号并行可选/逐条 `set_applied`/运行日志/自动回写结果到 Excel/结束汇总弹窗/手动回写重试按钮、④ 账号管理表格/弹窗/按钮/快捷方式与状态栏、密码明文保存提示、⑤ 设置 AI 模型同列上下布局、两列详情/参数表单、API Key 明文保存提示、`AIModelTestWorker` 后台测试连接、角色/生成参数/路径/端口配置、Shopee 更新安全和 dry-run/多账号并行设置并持久化 `config.json`;`app/workers.py` 已实现 `BaseWorker`、通用 signals、取消标记和 `QThread` 启动包装。
- 测试:`tests/` 已建立;T-006 后纯逻辑改动必须运行 `python -m unittest discover -s tests`,当前覆盖 appconfig/db/config/accounts/chrome 启动与快捷方式/editor 登录检测与商品 tab 生命周期、更新成功后关闭本轮新开 tab、满 9 张封面备份缺失阻断/删除确认/上传拖首位 mock 路径/excel 导入/旧字段与更新结果回写/ai 标题与封面 HTTP 解析/`generate_batch` 正常、失败与停止/prompts 读写与渲染/gui ① 导入采集/gui ② AI生成布局筛选、提示词管理、生成 worker 与双击预览/gui ③ 更新shopee筛选列表、确认弹窗、Shopee 更新安全拦截、`ApplyWorker` 串行/dry-run/按账号并行/端口冲突预检、运行日志、结果回写与汇总/gui ④ 账号管理、密码打码与明文保存提示/gui ⑤ AI 模型管理、API Key 打码与明文保存提示、角色/生成参数设置、Shopee 更新安全设置、dry-run/多账号并行设置/worker signal 与线程包装;CDP/Shopee 改动仍需测试商品手动验证。 - 测试:`tests/` 已建立;T-006 后纯逻辑改动必须运行 `python -m unittest discover -s tests`,当前覆盖 appconfig/db/config/accounts/chrome 启动与快捷方式/editor 登录检测与商品 tab 生命周期、更新成功后关闭本轮新开 tab、满 9 张封面备份缺失阻断/删除确认/上传拖首位 mock 路径/excel 导入/旧字段与更新结果回写/ai 标题与封面 HTTP 解析/`generate_batch` 正常、失败与停止/prompts 读写与渲染/gui ① 导入采集/gui ② AI生成布局筛选、提示词管理、生成 worker 与双击预览/gui ③ 更新shopee筛选列表、确认弹窗、Shopee 更新安全拦截、`ApplyWorker` 串行/dry-run/按账号并行/端口冲突预检、运行日志、结果回写与汇总/gui ④ 账号管理、密码打码与明文保存提示/gui ⑤ AI 模型管理、API Key 打码与明文保存提示、角色/生成参数设置、Shopee 更新安全设置、dry-run/多账号并行设置/worker signal 与线程包装;CDP/Shopee 改动仍需测试商品手动验证。
- 数据:`config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 已由 `.gitignore` 排除;密码与 API Key 本地明文保存但保存/变更时提示,UI 打码,日志/导出必须脱敏;运营填写后的 Excel 业务文件默认忽略,标准空模板 `shopee待处理任务模板.xlsx` 可提交;`app/appconfig.py` 首次读取缺失的 `config.json` 时会在本地写默认配置,`app/db.py` 调用 `init_db()` 时会在本地创建 SQLite DB。 - 数据:`config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 已由 `.gitignore` 排除;密码与 API Key 本地明文保存但保存/变更时提示,UI 打码,日志/导出必须脱敏;运营填写后的 Excel 业务文件默认忽略,标准空模板 `shopee待处理任务模板.xlsx` 可提交;`app/appconfig.py` 首次读取缺失的 `config.json` 时会在本地写默认配置,`app/db.py` 调用 `init_db()` 时会在本地创建 SQLite DB。
+1 -1
View File
@@ -129,7 +129,7 @@
## ⑤ 设置 ## ⑤ 设置
- AI 模型管理(T-501 已接入):左侧模型下拉 + 新增/删除,右侧详情编辑。 - AI 模型管理(T-501 已接入):同列上下布局,AI 模型下拉 + 新增/删除在“模型详情”上方;详情与设置表单尽量两项一行,长字段独占整行。
- 字段:启用、服务商名、类别(文本/图像)、api_type、模型ID、网址、密钥、连接超时。 - 字段:启用、服务商名、类别(文本/图像)、api_type、模型ID、网址、密钥、连接超时。
- 密钥存在 `config/ai_models.json`,本地明文保存;保存/变更时提示;UI 使用密码框打码显示,不进入日志/导出。 - 密钥存在 `config/ai_models.json`,本地明文保存;保存/变更时提示;UI 使用密码框打码显示,不进入日志/导出。
- 保存复用 `appconfig` 模型清单后端,保持 name 唯一、至少 text/image 各一个模型。 - 保存复用 `appconfig` 模型清单后端,保持 name 唯一、至少 text/image 各一个模型。
+17 -19
View File
@@ -18,31 +18,29 @@
<text class="tabT" x="330" y="68">③ 更新shopee</text><text class="tabT" x="492" y="68">④ 账号管理</text> <text class="tabT" x="330" y="68">③ 更新shopee</text><text class="tabT" x="492" y="68">④ 账号管理</text>
<text class="tabTA" x="640" y="68">⑤ 设置</text> <text class="tabTA" x="640" y="68">⑤ 设置</text>
<!-- A. AI 模型管理 (master-detail) --> <!-- A. AI 模型管理 (single-column master-detail) -->
<g transform="translate(18,94)"> <g transform="translate(18,94)">
<rect class="panel" x="0" y="0" width="904" height="170" rx="6"/> <rect class="panel" x="0" y="0" width="904" height="170" rx="6"/>
<text class="sec" x="14" y="24">AI 模型管理</text> <text class="sec" x="14" y="24">AI 模型管理</text>
<!-- left: model selector --> <!-- top: model selector -->
<rect class="sub" x="14" y="36" width="250" height="120" rx="6"/> <text class="lbl" x="14" y="58">AI 模型</text>
<text class="lbl" x="26" y="58">模型</text> <rect class="inp" x="76" y="42" width="350" height="26" rx="5"/><text class="cell" x="86" y="59">Nano Banana 2 · 图像 ▼</text>
<rect class="inp" x="26" y="64" width="226" height="26" rx="5"/><text class="cell" x="36" y="81">Nano Banana 2 ▼</text> <rect class="btn" x="440" y="42" width="70" height="26" rx="5"/><text class="btnT" x="458" y="59">新增</text>
<rect class="btn" x="26" y="98" width="70" height="26" rx="5"/><text class="btnT" x="44" y="115">新增</text> <rect class="btn" x="518" y="42" width="70" height="26" rx="5"/><text class="btnT" x="536" y="59">删除</text>
<rect class="btn" x="102" y="98" width="70" height="26" rx="5"/><text class="btnT" x="120" y="115">删除</text> <text class="mut" x="606" y="59">至少各 1 个 文本/图像模型</text>
<rect class="btnP" x="178" y="98" width="74" height="26" rx="5"/><text class="btnPT" x="190" y="115">测试连接</text>
<text class="mut" x="26" y="142">至少各 1 个 文本/图像模型</text>
<!-- right: detail form --> <!-- detail form below selector -->
<text class="lbl" x="286" y="56">服务商名称</text><rect class="inp" x="372" y="42" width="180" height="24" rx="4"/><text class="cell" x="382" y="59">Nano Banana 2</text> <text class="sec" x="14" y="92">模型详情</text>
<text class="lbl" x="572" y="56">网址</text><rect class="inp" x="612" y="42" width="278" height="24" rx="4"/><text class="cell" x="620" y="59" font-size="11" fill="#586073">https://api.vectorengine.ai/v1/chat/completions</text> <text class="lbl" x="14" y="122">服务商名</text><rect class="inp" x="86" y="106" width="210" height="24" rx="4"/><text class="cell" x="96" y="123">Nano Banana 2</text>
<text class="lbl" x="318" y="122">类别</text><rect class="inp" x="360" y="106" width="100" height="24" rx="4"/><text class="cell" x="370" y="123">图像 ▼</text>
<text class="lbl" x="488" y="122">api_type</text><rect class="inp" x="548" y="106" width="100" height="24" rx="4"/><text class="cell" x="558" y="123">auto ▼</text>
<text class="lbl" x="676" y="122">连接超时</text><rect class="inp" x="744" y="106" width="56" height="24" rx="4"/><text class="cell" x="756" y="123">30</text><text class="mut" x="806" y="123">秒</text>
<text class="lbl" x="286" y="92">类别</text><rect class="inp" x="372" y="78" width="92" height="24" rx="4"/><text class="cell" x="382" y="95">图像 ▼</text> <text class="lbl" x="14" y="152">网址</text><rect class="inp" x="86" y="136" width="360" height="24" rx="4"/><text class="cell" x="96" y="153" font-size="11" fill="#586073">https://api.vectorengine.ai/v1/chat/completions</text>
<text class="lbl" x="478" y="92">api_type</text><rect class="inp" x="538" y="78" width="100" height="24" rx="4"/><text class="cell" x="548" y="95">auto ▼</text> <text class="lbl" x="466" y="152">密钥</text><rect class="inp" x="510" y="136" width="180" height="24" rx="4"/><text class="cell" x="520" y="153" fill="#8a92a0">••••••••</text>
<text class="lbl" x="656" y="92">连接超时</text><rect class="inp" x="724" y="78" width="56" height="24" rx="4"/><text class="cell" x="736" y="95">30</text><text class="mut" x="786" y="95">秒</text> <rect class="btn" x="706" y="136" width="86" height="24" rx="4"/><text class="btnT" x="722" y="153">保存</text>
<rect class="btnP" x="802" y="136" width="88" height="24" rx="4"/><text class="btnPT" x="820" y="153">测试连接</text>
<text class="lbl" x="286" y="128">模型ID</text><rect class="inp" x="372" y="114" width="266" height="24" rx="4"/><text class="cell" x="382" y="131" font-size="11" fill="#586073">gemini-3.1-flash-image-preview</text>
<text class="lbl" x="656" y="128">密钥</text><rect class="inp" x="724" y="114" width="140" height="24" rx="4"/><text class="cell" x="734" y="131" fill="#8a92a0">••••••••</text>
<rect class="btn" x="868" y="114" width="22" height="24" rx="4"/><text class="btnT" x="874" y="131">👁</text>
</g> </g>
<!-- B. 角色 + 生成参数 --> <!-- B. 角色 + 生成参数 -->

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

+7
View File
@@ -660,3 +660,10 @@
- 原因:本地 gitignored 的 `config/ai_models.json` 来自旧数据或手工维护,模型缺少 `category`,而当前 schema 要求每个模型必须是 `text` 或 `image`。 - 原因:本地 gitignored 的 `config/ai_models.json` 来自旧数据或手工维护,模型缺少 `category`,而当前 schema 要求每个模型必须是 `text` 或 `image`。
- 处理:已在本机只补齐模型 `category` 和空的 `enabled=true`,保留原有 url/model/API Key;验证 `appconfig.list_ai_models()` 可正常加载,且 `config/ai_models.json` 仍被 gitignore。 - 处理:已在本机只补齐模型 `category` 和空的 `enabled=true`,保留原有 url/model/API Key;验证 `appconfig.list_ai_models()` 可正常加载,且 `config/ai_models.json` 仍被 gitignore。
- 文档:新增 `docs/troubleshooting.md`,并在 `docs/README.md`、`docs/03-tech-stack.md`、`docs/current-state.md` 增加索引和说明。排查命令只输出 `name/category/api_type/enabled/has_api_key`,不输出 API Key。 - 文档:新增 `docs/troubleshooting.md`,并在 `docs/README.md`、`docs/03-tech-stack.md`、`docs/current-state.md` 增加索引和说明。排查命令只输出 `name/category/api_type/enabled/has_api_key`,不输出 API Key。
## 【2026-06-29】设置页 AI 模型布局微调
- 状态:DONE
- 变更:`app/gui.py` 将⑤设置页的 AI 模型下拉/新增/删除从左侧分栏移到“模型详情”上方,设置页改为单列滚动布局;模型详情、角色与生成参数、路径与端口、Shopee 更新安全表单尽量两项一行,URL/API Key/Chrome 路径等长字段独占整行。
- 文档:同步 `docs/routes.md`、`docs/api.md`、`docs/current-state.md` 中的设置页布局描述,并更新 `docs/ui/tab5-settings.svg` UI 草图。
- 验证:`python -m compileall app main.py tests` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(48 tests)。