refactor(chat): R08-T01..T06 — chat_panel.py 1821 -> 345, composer 663 -> 11

presentation/chat/
      chat_history_widget.py   348  T01  mạch hội thoại (từ ui/chat_view.py)
      chat_bubble_style.py     202  T01  cách vẽ bong bóng, diff, đường thời gian
      composer_widget.py       364  T02  thanh công cụ quanh ô nhập
      chat_input_box.py        328  T02  ô nhập: Ctrl+Enter, dán ảnh, popup /skill
      attachment_picker.py     215  T03  đọc tệp đính kèm + chặn theo chính sách
      chat_output_panel.py     186  T05  theo dõi thư mục output, hiện tệp mới
      chat_turn_runner.py      281  T06  chạy một lượt
      chat_event_stream.py     228  T06  nhận sự kiện phát về từ luồng nền
      chat_session_store.py    413  T06  lưu/nạp phiên, đếm token, nối lại lượt
      chat_agents.py           246  T06  chọn agent, skill, định tuyến model
      chat_panel_layout.py     148  T06  bố cục hai cột
      chat_helpers.py           53  T06  hàm và bảng tra dùng chung
    ui/chat_panel.py           345  __init__ + trạng thái
    ui/chat_view.py             10  vỏ chuyển tiếp
    ui/composer.py              11  vỏ chuyển tiếp

R08-T04 KHÔNG LÀM ĐƯỢC: plan đòi audio_recorder_widget.py, nhưng trong repo
KHÔNG CÓ chức năng ghi âm nào — grep 'audio|record|voice|micro' toàn ui/ chỉ
ra chữ 'record' trong nghĩa 'ghi lại transcript'. Không có gì để tách, và tôi
không dựng một widget mới nhân danh refactor. Giống hệt trường hợp
connector_settings_widget.py ở T07.

_start_turn (144 dòng) và _on_event (127) để nguyên có chủ ý: cái đầu dựng
trọn ngữ cảnh một lượt rồi giao cho luồng nền, cái sau phân nhánh theo loại sự
kiện. Cắt nhỏ thì phải chuyền hàng chục biến trạng thái qua lại, đọc khó hơn.

Hai lỗi tự gây, cả hai đều do script:
* regex bỏ import cũ chỉ cắt DÒNG ĐẦU của một import nhiều dòng, để lại phần
  đuôi mồ côi -> IndentationError.
* _build_layout dùng biến 'root' vốn cục bộ trong __init__. Bộ test bắt được
  cái này (2 bài integration đỏ), không phải checker — vì nó là lỗi dựng
  widget, không phải lỗi hình học.

756 test xanh. 24/24 checker qua.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nam Pham Dinh Thanh
2026-08-28 01:08:31 +09:00
co-authored by Claude Opus 5
parent f0fd3a41cd
commit 577b81a641
15 changed files with 3050 additions and 2657 deletions
+53
View File
@@ -0,0 +1,53 @@
"""Hàm và bảng tra dùng chung trong khung chat — R08-T06.
Thuần hàm, không widget. Gom về đây vì cả năm file trong gói đều hỏi tới.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List, Optional
from PySide6.QtCore import Qt, QTimer, Signal
from PySide6.QtCore import QFileSystemWatcher
from PySide6.QtWidgets import (
QComboBox, QHBoxLayout, QLabel, QMessageBox, QPushButton, QSplitter,
QVBoxLayout, QWidget,
)
from ...core.worker import AgentWorker
from ...i18n import on_language_changed, tr
from ...state import AppContext
from ...theme import current_palette
from ...ui.chat_view import ChatView, ThinkingIndicator
from ...ui.composer import Composer
from ...ui.icons import collapse_right_icon, icon as app_icon
from ...ui.osutil import is_image, open_path
from ...ui.widgets import CollapsibleSection, CollapseStrip, PlanSection
def _format_plan_steps(steps) -> str:
"""Render plan steps ``[{title, status}]`` as an icon checklist for the chat."""
lines = []
for s in steps or []:
title = str((s or {}).get("title", "")).strip()
if not title:
continue
icon = _PLAN_ICONS.get(str((s or {}).get("status", "pending")).lower(), "○")
lines.append(f"{icon} {title}")
return "\n".join(lines)
def _is_scratch(path: str) -> bool:
"""True for helper/intermediate files (kept out of the Output list)."""
try:
return ".scratch" in Path(path).parts
except Exception: # noqa: BLE001
return False
_TOOL_STATUS = {
"save_file": "chat.creating",
"write_file": "chat.creating",
"run_command": "chat.creating",
"edit_file": "chat.editing",
"install_package": "chat.installing",
"read_file": "chat.reading",
}