feat(ai-outfit): 话术按钮行微调 + 保留插入标题 + 最终提示词预览改弹窗 (§19.20)
话术组小改版(保留 {title} 占位符、不自动前置):
- 「保存话术」→「保存」,从编辑框下方上移到模板按钮行「重命名」之后
(行变 新建/另存为/重命名/保存/删除)
- 保留「插入标题」按钮 + {title} 占位符(用户自定标题位置,不与旧话术重复)
- 最终提示词预览改按需弹窗:编辑框下方「预览最终提示词」按钮 → 非模态
_OutfitPreviewDialog,含数据行下拉(read_all_rows,状态无关)+ 只读替换后
提示词(含 §7.1 输出要求);话术/分辨率/数据行变化时实时刷新
- 左栏空间理由:标题生成组 + 话术组并存,常驻预览会逼出滚动;弹窗零常驻高度
render_prompt 与 DEFAULT_OUTFIT_PROMPT 不改(纯面板改动)。
测试:面板断言按钮行含「保存」、保留「插入标题」+「预览最终提示词」、
预览弹窗按选中行替换 {title} 并附输出要求。全套 py37 通过
(test_config_service 的 ai_models.json 顺序失败属并行 §19.13,与本改动无关)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QInputDialog,
|
||||
QDoubleSpinBox,
|
||||
QFileDialog,
|
||||
@@ -216,6 +217,79 @@ class _TitleWorker(QObject):
|
||||
self.finished.emit(success, fail)
|
||||
|
||||
|
||||
class _OutfitPreviewDialog(QDialog):
|
||||
"""Non-modal 最终提示词预览(docs/11 §7.3)。
|
||||
|
||||
数据行下拉 + 只读结果;内容 = render_prompt(话术, 选中行, 当前分辨率)。
|
||||
话术/分辨率/数据行变化时由面板调 refresh() 刷新。下拉用 read_all_rows
|
||||
(状态无关)填,整表完成后仍可选。
|
||||
"""
|
||||
|
||||
def __init__(self, panel):
|
||||
super().__init__(panel)
|
||||
self._panel = panel
|
||||
self.setWindowTitle("最终提示词预览")
|
||||
self.setModal(False)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.resize(440, 380)
|
||||
|
||||
v = QVBoxLayout(self)
|
||||
row = QHBoxLayout()
|
||||
row.addWidget(QLabel("数据行"))
|
||||
self._combo = QComboBox()
|
||||
panel._compact_combo(self._combo)
|
||||
self._combo.currentIndexChanged.connect(self.refresh)
|
||||
row.addWidget(self._combo, stretch=1)
|
||||
v.addLayout(row)
|
||||
|
||||
self._warn = QLabel("")
|
||||
self._warn.setStyleSheet("color:#b87a00;")
|
||||
self._warn.setVisible(False)
|
||||
v.addWidget(self._warn)
|
||||
|
||||
self._view = QPlainTextEdit()
|
||||
self._view.setReadOnly(True)
|
||||
v.addWidget(self._view, stretch=1)
|
||||
|
||||
self.reload_rows()
|
||||
|
||||
def reload_rows(self):
|
||||
"""(Re)fill the data-row dropdown from the panel's Excel (status-independent)."""
|
||||
excel = self._panel._excel_edit.text().strip()
|
||||
rows = []
|
||||
if excel:
|
||||
try:
|
||||
from services.excel_service import read_all_rows
|
||||
rows = read_all_rows(excel)
|
||||
except Exception as exc: # noqa: BLE001 - silent for preview
|
||||
logger.info("Preview rows unavailable: %s", exc)
|
||||
self._combo.blockSignals(True)
|
||||
self._combo.clear()
|
||||
if rows:
|
||||
for t in rows:
|
||||
pid = t.product_id if (t.product_id and t.product_id.strip()) else "(无货号)"
|
||||
self._combo.addItem(
|
||||
"第 {} 行 · {} · {}".format(t.row_index, pid, t.title), t)
|
||||
else:
|
||||
self._combo.addItem("(选 Excel 后显示替换效果)", None)
|
||||
self._combo.blockSignals(False)
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
from core.ai_outfit import build_output_requirements, render_prompt
|
||||
template = self._panel._prompt_edit.toPlainText()
|
||||
resolution = self._panel._resolution.currentText()
|
||||
missing = "{title}" not in template
|
||||
self._warn.setVisible(missing)
|
||||
if missing:
|
||||
self._warn.setText("⚠ 话术缺少 {title} 占位符,标题不会带入")
|
||||
task = self._combo.currentData()
|
||||
if task is None:
|
||||
self._view.setPlainText(template + build_output_requirements(resolution))
|
||||
else:
|
||||
self._view.setPlainText(render_prompt(template, task, resolution))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main panel
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -235,6 +309,7 @@ class AiOutfitPanel(QWidget):
|
||||
self._title_worker = None
|
||||
self._title_fail = 0 # running failure count for title stats
|
||||
self._title_model_name = "" # configured 标题模型 name (§17.3 配置定名)
|
||||
self._preview_dialog = None # 最终提示词预览弹窗(§7.3,按需)
|
||||
self._row_to_table = {} # excel row_index -> table row
|
||||
self._failures = [] # list of OutfitResult (failed)
|
||||
self._last_resolution = "" # for "value actually changed" check (§10.2)
|
||||
@@ -348,8 +423,10 @@ class AiOutfitPanel(QWidget):
|
||||
prompt_header.addWidget(self._prompt_combo, stretch=1)
|
||||
pv.addLayout(prompt_header)
|
||||
trow = QHBoxLayout()
|
||||
# 「保存」插在「重命名」「删除」之间(§7.2,原编辑框下方的「保存话术」上移改名)
|
||||
for text, slot in (("新建", self._prompt_new), ("另存为", self._prompt_save_as),
|
||||
("重命名", self._prompt_rename), ("删除", self._prompt_delete)):
|
||||
("重命名", self._prompt_rename), ("保存", self._save_prompt),
|
||||
("删除", self._prompt_delete)):
|
||||
b = QPushButton(text)
|
||||
if text == "删除":
|
||||
b.setObjectName("aiPromptDeleteBtn")
|
||||
@@ -358,15 +435,16 @@ class AiOutfitPanel(QWidget):
|
||||
pv.addLayout(trow)
|
||||
self._prompt_edit = QPlainTextEdit()
|
||||
self._prompt_edit.setMinimumHeight(150)
|
||||
self._prompt_edit.textChanged.connect(self._refresh_preview_dialog)
|
||||
pv.addWidget(self._prompt_edit)
|
||||
prow = QHBoxLayout()
|
||||
insert_title_btn = QPushButton("插入标题")
|
||||
insert_title_btn.clicked.connect(
|
||||
lambda: self._prompt_edit.insertPlainText("{title}"))
|
||||
prow.addWidget(insert_title_btn)
|
||||
save_btn = QPushButton("保存话术")
|
||||
save_btn.clicked.connect(self._save_prompt)
|
||||
prow.addWidget(save_btn)
|
||||
preview_btn = QPushButton("预览最终提示词")
|
||||
preview_btn.clicked.connect(self._open_preview)
|
||||
prow.addWidget(preview_btn)
|
||||
pv.addLayout(prow)
|
||||
col.addWidget(prm, stretch=3)
|
||||
|
||||
@@ -419,6 +497,8 @@ class AiOutfitPanel(QWidget):
|
||||
self._retry_count.setRange(0, 10)
|
||||
self._resolution = QComboBox()
|
||||
self._resolution.addItems(_RESOLUTIONS)
|
||||
# Keep the preview dialog's output-requirements block in sync (§7.3).
|
||||
self._resolution.currentIndexChanged.connect(self._refresh_preview_dialog)
|
||||
# activated = user click only; programmatic sets won't pop (docs/11 §10.2)
|
||||
self._resolution.activated.connect(self._on_resolution_activated)
|
||||
self._quality = QComboBox()
|
||||
@@ -631,6 +711,8 @@ class AiOutfitPanel(QWidget):
|
||||
if path:
|
||||
self._excel_edit.setText(path)
|
||||
self._emit_config()
|
||||
if self._preview_dialog is not None:
|
||||
self._preview_dialog.reload_rows()
|
||||
|
||||
def _browse_output(self):
|
||||
path = QFileDialog.getExistingDirectory(
|
||||
@@ -643,6 +725,27 @@ class AiOutfitPanel(QWidget):
|
||||
self._store_current_text()
|
||||
self.statusBar_message("话术已保存")
|
||||
|
||||
# -- 最终提示词预览弹窗(§7.3)--------------------------------------
|
||||
|
||||
def _open_preview(self):
|
||||
"""Open (or raise) the non-modal 最终提示词预览 dialog."""
|
||||
if self._preview_dialog is not None:
|
||||
self._preview_dialog.raise_()
|
||||
self._preview_dialog.activateWindow()
|
||||
return
|
||||
dialog = _OutfitPreviewDialog(self)
|
||||
dialog.finished.connect(self._on_preview_closed)
|
||||
self._preview_dialog = dialog
|
||||
dialog.show()
|
||||
|
||||
def _on_preview_closed(self, _result):
|
||||
self._preview_dialog = None
|
||||
|
||||
def _refresh_preview_dialog(self):
|
||||
"""Refresh the preview if open (话术/分辨率/数据行变化时;否则 no-op)."""
|
||||
if self._preview_dialog is not None:
|
||||
self._preview_dialog.refresh()
|
||||
|
||||
# -- 话术模板(§7.2)------------------------------------------------
|
||||
|
||||
def _rebuild_prompt_combo(self):
|
||||
|
||||
Reference in New Issue
Block a user