Skip to content

feat(chat-theme): expand Chat UI customization#240

Draft
RachelForster wants to merge 6 commits into
mainfrom
feat/customize-chat-ui
Draft

feat(chat-theme): expand Chat UI customization#240
RachelForster wants to merge 6 commits into
mainfrom
feat/customize-chat-ui

Conversation

@RachelForster

Copy link
Copy Markdown
Owner

Summary

  • replace the limited Chat theme controls with a schema-driven editor covering the full token surface
  • add inheritance, section reset, undo/redo, font controls, asset management, protected deletion, and theme export
  • add responsive multi-state previews with diagnostics for contrast, missing assets, and mobile overflow
  • persist session appearance as reusable themes with explicit global/theme/session precedence
  • add JSON-only, host-rendered Chat UI plugin slots and server-side plugin actions

Why

The existing Chat UI customization flow exposed only part of the theme model and did not provide a safe extension surface for plugins. This change makes the full theme schema editable, adds production-grade asset and preview workflows, and keeps plugin UI rendering under host control.

User and developer impact

Users can build, preview, diagnose, save, and export substantially richer Chat themes. Plugin authors can contribute safe Chat actions and output cards without injecting arbitrary HTML, JavaScript, React nodes, or CSS.

Validation

  • repository pre-push Python suite passed
  • frontend formatting and TypeScript project checks passed
  • frontend unit suite passed: 111 files, 701 tests
  • production frontend build passed

"kind": _theme_asset_kind(asset),
"name": asset.name,
"path": relative,
"size": asset.stat().st_size,
if _read_manifest(theme_dir) is None:
raise FileNotFoundError(f"主题不存在或无效:{safe_id}")
rows = []
for asset in theme_dir.rglob("*"):
raise ValueError("单个主题资源不能超过 16 MiB")

assets_dir = safe_child_path(theme_dir, "assets")
assets_dir.mkdir(parents=True, exist_ok=True)
filename = f"{stem}{suffix}"
destination = safe_child_path(assets_dir, filename)
counter = 2
while destination.exists():
while destination.exists():
destination = safe_child_path(assets_dir, f"{stem}-{counter}{suffix}")
counter += 1
shutil.copy2(source, destination)
if _manifest_references_asset(manifest, asset_path):
raise ValueError("该资源仍被主题引用,请先清除对应字段并保存主题")
target = safe_child_path(theme_dir, asset_path)
if not target.is_file():
target = safe_child_path(theme_dir, asset_path)
if not target.is_file():
raise FileNotFoundError(f"主题资源不存在:{asset_path}")
target.unlink()
raise FileNotFoundError(f"主题资源不存在:{asset_path}")
target.unlink()
parent = target.parent
while parent != theme_dir and parent.is_dir() and not any(parent.iterdir()):
target.unlink()
parent = target.parent
while parent != theme_dir and parent.is_dir() and not any(parent.iterdir()):
parent.rmdir()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants