Delta team/fix comment ui #10
@@ -59,9 +59,19 @@ class AttachmentMixin:
|
||||
lines = [text] if text else []
|
||||
|
||||
# --- User-attached files ---
|
||||
# Đường dẫn đã giải quyết của các tệp đính kèm, để vòng quét thư mục
|
||||
# phía sau không gửi lại chính chúng một lần nữa.
|
||||
da_dinh_kem = set()
|
||||
if has_attachments:
|
||||
lines.append("\n[Attachments] — read and use these files to answer the request:")
|
||||
lines.append(
|
||||
"\n[Attachments] — the user attached these files for THIS request. "
|
||||
"They are the PRIMARY subject: read them in full and base the answer "
|
||||
"on them. Anything listed further below is background context only.")
|
||||
for p in attachments:
|
||||
try:
|
||||
da_dinh_kem.add(str(Path(p).resolve()))
|
||||
except OSError:
|
||||
pass
|
||||
lines.extend(self._read_one_attachment(p, limit, notify))
|
||||
|
||||
# --- Auto-load existing workspace/output folder files as input data ---
|
||||
@@ -74,10 +84,10 @@ class AttachmentMixin:
|
||||
if workspace is not None:
|
||||
lines.extend(self._folder_input_lines(
|
||||
workspace,
|
||||
"[Workspace files] — existing files in output folder, "
|
||||
"read and use as input data. The user expects you to "
|
||||
"process these files automatically:",
|
||||
limit, max_files, notify))
|
||||
"[Workspace files] — other files that happen to sit in the output "
|
||||
"folder. Background context; do NOT let them displace the "
|
||||
"attached files or the user's own question:",
|
||||
limit, max_files, notify, da_dinh_kem))
|
||||
|
||||
# --- Project knowledge (Claude-Projects style) ---
|
||||
# Only scanned separately when it's a DIFFERENT folder from the
|
||||
@@ -88,31 +98,44 @@ class AttachmentMixin:
|
||||
if knowledge is not None and knowledge != workspace:
|
||||
lines.extend(self._folder_input_lines(
|
||||
knowledge,
|
||||
"[Project files] — shared knowledge files of this project, "
|
||||
"available to every conversation in it. Read and use them "
|
||||
"as context for the request:",
|
||||
limit, max_files, notify))
|
||||
"[Project files] — shared knowledge of this project. Background "
|
||||
"context; do NOT let them displace the attached files or "
|
||||
"the user's own question:",
|
||||
limit, max_files, notify, da_dinh_kem))
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _folder_input_lines(self, folder: Path, header: str, limit: int,
|
||||
max_files: int, notify=None) -> list:
|
||||
max_files: int, notify=None, skip=frozenset()) -> list:
|
||||
"""Embed a folder's readable files into the prompt — recursing into
|
||||
every sub-folder, any depth, not just the top level, so files placed
|
||||
in nested folders are read and processed too (same per-message file
|
||||
cap as manual attachments — Settings → Attachments → max files;
|
||||
0 = unlimited — so a folder with dozens of files can't blow the
|
||||
context window)."""
|
||||
from pathlib import Path as _P
|
||||
|
||||
from ...core.doc_extract import find_input_files
|
||||
|
||||
out: list = []
|
||||
shown, total = find_input_files(folder, self._INPUT_EXTS, max_files)
|
||||
# Bo qua tep nguoi dung DA dinh kem tuong minh. Tep dinh kem thuong nam
|
||||
# ngay trong thu muc workspace, nen khong loc thi cung mot tai lieu di vao
|
||||
# prompt HAI lan: mot lan duoi [Attachments], mot lan duoi [Workspace
|
||||
# files]. Voi tai lieu dai, ban thu hai vua nhan doi ngu canh vua khien
|
||||
# model khong biet ban nao la ban duoc hoi.
|
||||
# Số tệp thư mục này thực sự trả về, ĐO TRƯỚC khi lọc trùng: dòng cảnh
|
||||
# báo bên dưới nói về giới hạn mỗi lượt, nên đếm cả tệp bị lọc vì đã
|
||||
# đính kèm sẽ báo sai là "không nạp được".
|
||||
so_lay_duoc = len(shown)
|
||||
if skip:
|
||||
shown = [f for f in shown if str(_P(f).resolve()) not in skip]
|
||||
if shown:
|
||||
out.append("\n" + header)
|
||||
for f in shown:
|
||||
out.extend(self._read_one_attachment(str(f), limit, notify))
|
||||
if total > len(shown):
|
||||
skipped = total - len(shown)
|
||||
if total > so_lay_duoc:
|
||||
skipped = total - so_lay_duoc
|
||||
out.append(f"…({skipped} more files in the folder were not "
|
||||
"loaded — per-message attachment limit; mention a "
|
||||
"file by name if the user asks about it)")
|
||||
|
||||
@@ -191,10 +191,9 @@ class TopBarMixin:
|
||||
# Reload the Cowork tab's Agent (Model) list for the newly selected provider.
|
||||
self.cowork.refresh_agents()
|
||||
self.workspace.refresh_ai_models() # + the Folder AI-edit model picker
|
||||
self.statusBar().showMessage(
|
||||
tr("app.status.using_provider",
|
||||
label=PROVIDER_LABELS.get(self.ctx.config.active_provider))
|
||||
)
|
||||
# Khong bao "dang dung <provider>" o thanh trang thai: chinh bo chon
|
||||
# provider nam ngay tren man hinh va da hien thu vua chon, nen dong thong
|
||||
# bao chi nhac lai mot thu nguoi dung vua tu tay lam.
|
||||
def _on_language_changed(self, _idx: int) -> None:
|
||||
"""Đổi ngôn ngữ giao diện; trùng ngôn ngữ hiện tại thì bỏ qua để không dựng lại
|
||||
toàn bộ chữ vô ích.
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
"""Sáu chỗ chỉnh nhỏ trên giao diện, mỗi bài chốt đúng một triệu chứng đã báo.
|
||||
|
||||
Gom một file vì chúng không chia sẻ gì ngoài việc đều là phản hồi từ người dùng
|
||||
trong cùng một vòng; tách sáu file cho sáu khẳng định chỉ tạo thêm chỗ để tìm.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
# ---- bỏ nhãn "provider · model" cạnh chữ Cowork --------------------------
|
||||
|
||||
def test_thanh_cong_cu_cowork_khong_con_nhan_provider():
|
||||
"""Nó lặp lại thứ bộ chọn provider ở thanh trên đang hiển thị."""
|
||||
src = (REPO / "ui" / "cowork_tab.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "model_lbl" not in src
|
||||
assert "PROVIDER_LABELS" not in src, "import đã thành vô dụng thì phải gỡ"
|
||||
|
||||
|
||||
# ---- không báo trạng thái khi đổi provider -------------------------------
|
||||
|
||||
def test_doi_provider_khong_bao_o_thanh_trang_thai():
|
||||
"""Bộ chọn nằm ngay trên màn hình và đã hiện thứ vừa chọn."""
|
||||
src = (REPO / "presentation" / "shell" / "top_bar.py").read_text(encoding="utf-8")
|
||||
than_ham = src.split("def _on_provider_changed")[1].split("def _on_language_changed")[0]
|
||||
|
||||
assert "showMessage" not in than_ham
|
||||
assert "using_provider" not in than_ham
|
||||
|
||||
|
||||
# ---- tên project mặc định không gắn ngôn ngữ ----------------------------
|
||||
|
||||
def test_ten_project_mac_dinh_khong_qua_tr():
|
||||
"""Tên project được GHI XUỐNG ĐĨA.
|
||||
|
||||
Tạo project lúc đang ở tiếng Nhật thì tên nó thành "新規プロジェクト" vĩnh
|
||||
viễn, và đổi ngôn ngữ về tiếng Việt không sửa được — đó là dữ liệu, không
|
||||
phải chữ giao diện. Người dùng nhìn thấy chữ Nhật trên màn tiếng Việt và
|
||||
tưởng là lỗi hiển thị.
|
||||
"""
|
||||
from cowork_local.presentation.workspace import project_editing as pe
|
||||
|
||||
assert pe._DEFAULT_PROJECT_NAME.isascii(), "tên mặc định phải trung tính"
|
||||
src = (REPO / "presentation" / "workspace" / "project_editing.py").read_text(encoding="utf-8")
|
||||
than_ham = src.split("def _create")[1].split("def _delete")[0]
|
||||
assert 'tr("workspace.default_new_name")' not in than_ham
|
||||
|
||||
|
||||
def test_ten_mac_dinh_khong_trung_nhau(monkeypatch):
|
||||
"""Bấm "Project mới" hai lần liên tiếp không được ra hai tên giống nhau."""
|
||||
from cowork_local.presentation.workspace.project_editing import (
|
||||
ProjectEditingMixin, _DEFAULT_PROJECT_NAME,
|
||||
)
|
||||
import cowork_local.core.projects as projects
|
||||
|
||||
class _P:
|
||||
def __init__(self, pid, name):
|
||||
self.project_id, self.name = pid, name
|
||||
|
||||
da_co = [_P("p1", _DEFAULT_PROJECT_NAME)]
|
||||
monkeypatch.setattr(projects, "list_projects", lambda: da_co)
|
||||
|
||||
class _K:
|
||||
_name_taken = ProjectEditingMixin._name_taken
|
||||
|
||||
assert _K()._name_taken(_DEFAULT_PROJECT_NAME) is True
|
||||
assert _K()._name_taken(f"{_DEFAULT_PROJECT_NAME} (2)") is False
|
||||
|
||||
|
||||
# ---- "Tất cả project…" phải hiện MỌI project ----------------------------
|
||||
|
||||
def test_tat_ca_project_xoa_bo_loc_theo_project():
|
||||
"""Bảng lịch sử nhúng trong Cowork của MỘT project nên bị lọc theo project đó.
|
||||
|
||||
Vào bằng link "Tất cả project…" mà còn bộ lọc thì tạo 5 project chỉ thấy 1.
|
||||
"""
|
||||
src = (REPO / "presentation" / "shell" / "rail_project.py").read_text(encoding="utf-8")
|
||||
than_ham = src.split("def goto_all_projects")[1].split("def _on_rail_recent")[0]
|
||||
|
||||
assert 'set_project_filter("")' in than_ham
|
||||
|
||||
|
||||
# ---- ba nút quản lý project về cùng một hàng ----------------------------
|
||||
|
||||
def test_sua_va_luu_project_cung_hang_voi_project_moi(qapp, tmp_path):
|
||||
"""Trước đó "Lưu project" nằm dưới cùng khung bên phải, cách "Project mới"
|
||||
gần hết chiều cao màn hình."""
|
||||
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
||||
from cowork_local.presentation.shell.main_window import MainWindow
|
||||
from cowork_local.presentation.workspace.project_editing import _row_layout_of
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
build_config(config_path)
|
||||
win = MainWindow(build_context(config_path))
|
||||
try:
|
||||
ws = win.workspace
|
||||
hang = _row_layout_of(ws._new_btn)
|
||||
|
||||
assert hang is not None, "không tìm được hàng chứa nút Project mới"
|
||||
assert _row_layout_of(ws._edit_btn) is hang
|
||||
assert _row_layout_of(ws._save_btn) is hang
|
||||
# Thu tu doc tu trai sang: tao moi -> sua -> luu
|
||||
assert (hang.indexOf(ws._new_btn)
|
||||
< hang.indexOf(ws._edit_btn)
|
||||
< hang.indexOf(ws._save_btn))
|
||||
finally:
|
||||
win.close()
|
||||
|
||||
|
||||
def test_hai_nut_project_khong_hien_ngoai_sub_tab_project(qapp, tmp_path):
|
||||
"""Hàng tiêu đề vắt ngang CẢ màn Workspace.
|
||||
|
||||
Chuyển "Sửa project" + "Lưu project" lên đó (bug 10) làm chúng hiện luôn ở
|
||||
Cowork, Co4E, Thư mục và GraphRAG — nơi không có biểu mẫu project nào để sửa
|
||||
hay lưu. Đúng luật mà ``_new_btn`` đã theo từ trước.
|
||||
"""
|
||||
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
||||
from cowork_local.presentation.shell.main_window import MainWindow
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
build_config(config_path)
|
||||
win = MainWindow(build_context(config_path))
|
||||
try:
|
||||
ws = win.workspace
|
||||
ws._current_id = "gia-lap"
|
||||
|
||||
ws.tabs.setCurrentIndex(ws._project_tab_idx)
|
||||
ws._sync_project_buttons()
|
||||
assert ws._edit_btn.isHidden() is False
|
||||
assert ws._save_btn.isHidden() is False
|
||||
|
||||
if ws._cowork_tab_idx < 0:
|
||||
pytest.skip("bản dựng này không có sub-tab Cowork")
|
||||
ws.tabs.setTabVisible(ws._cowork_tab_idx, True)
|
||||
ws.tabs.setCurrentIndex(ws._cowork_tab_idx)
|
||||
ws._sync_project_buttons()
|
||||
|
||||
assert ws._edit_btn.isHidden() is True, "nút Sửa project lọt sang tab Cowork"
|
||||
assert ws._save_btn.isHidden() is True, "nút Lưu project lọt sang tab Cowork"
|
||||
finally:
|
||||
win.close()
|
||||
|
||||
|
||||
def test_chua_chon_project_thi_hai_nut_cung_an(qapp, tmp_path):
|
||||
"""Không có project nào đang mở thì cả Sửa lẫn Lưu đều vô nghĩa."""
|
||||
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
||||
from cowork_local.presentation.shell.main_window import MainWindow
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
build_config(config_path)
|
||||
win = MainWindow(build_context(config_path))
|
||||
try:
|
||||
ws = win.workspace
|
||||
ws._current_id = ""
|
||||
ws.tabs.setCurrentIndex(ws._project_tab_idx)
|
||||
ws._sync_project_buttons()
|
||||
|
||||
assert ws._edit_btn.isHidden() is True
|
||||
assert ws._save_btn.isHidden() is True
|
||||
finally:
|
||||
win.close()
|
||||
+6
-9
@@ -7,7 +7,6 @@ from PySide6.QtWidgets import (
|
||||
QFileDialog, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from ..config import PROVIDER_LABELS
|
||||
from ..core import agent_roles
|
||||
from ..core.worker import AgentWorker
|
||||
from ..i18n import on_language_changed, tr
|
||||
@@ -32,9 +31,6 @@ class CoworkTab(ChatPanel):
|
||||
|
||||
self._title_lbl = QLabel()
|
||||
self._title_lbl.setStyleSheet("font-weight:700; font-size:15px;")
|
||||
self.model_lbl = QLabel("")
|
||||
self.model_lbl.setObjectName("hint")
|
||||
|
||||
self.skills_btn = QPushButton()
|
||||
self.skills_btn.setIcon(icon("book"))
|
||||
self.skills_btn.clicked.connect(self._open_skills_manager)
|
||||
@@ -44,7 +40,6 @@ class CoworkTab(ChatPanel):
|
||||
self._new_btn.clicked.connect(self.new_session)
|
||||
|
||||
self.toolbar_layout.addWidget(self._title_lbl)
|
||||
self.toolbar_layout.addWidget(self.model_lbl)
|
||||
self.toolbar_layout.addStretch(1)
|
||||
self.toolbar_layout.addWidget(self.skills_btn)
|
||||
self.toolbar_layout.addWidget(self._new_btn)
|
||||
@@ -377,10 +372,12 @@ class CoworkTab(ChatPanel):
|
||||
self._apply_output_folder_label()
|
||||
|
||||
def refresh_header(self) -> None:
|
||||
"""Cập nhật dòng "provider · model" trên thanh công cụ Cowork."""
|
||||
cfg = self.ctx.config
|
||||
label = PROVIDER_LABELS.get(cfg.active_provider, cfg.active_provider)
|
||||
self.model_lbl.setText(f"{label} · {cfg.model_label()}")
|
||||
"""Cập nhật các nhãn trên thanh công cụ Cowork.
|
||||
|
||||
Dòng "provider · model" từng nằm ngay sau chữ "Cowork" đã được gỡ: nó
|
||||
lặp lại thông tin mà bộ chọn provider ở thanh trên đang hiển thị, và
|
||||
chiếm chỗ đắt nhất trên thanh công cụ cho một thứ chỉ để đọc.
|
||||
"""
|
||||
self._apply_output_folder_label() # picks up edits made via Settings too
|
||||
|
||||
def build_job(self, text: str, messages, out_dir):
|
||||
|
||||
Reference in New Issue
Block a user