Soát lại từng dòng plan thì thấy tôi báo R08 xong hơi sớm. Ba chỗ thiếu thật:
T06 ChatPanel vẫn ở ui/, plan đòi presentation/chat/chat_panel.py
T08 agents_admin_tab.py (498) và tools_admin_tab.py (245) vẫn ở ui/
presentation/chat/chat_panel.py 346
presentation/monitoring/tabs/agents_admin_tab.py 383
presentation/monitoring/tabs/agent_edit_dialog.py 143
presentation/monitoring/tabs/tools_admin_tab.py 245
ui/chat_panel.py / agents_admin_tab.py / tools_admin_tab.py ~10 mỗi cái
agents_admin_tab.py 498 dòng nên tách thêm agent_edit_dialog.py: bảng danh
sách và hộp thoại sửa là hai việc, và hộp thoại còn tự đi hỏi provider xem có
model nào — thứ bảng không cần biết.
BA CHỖ CÒN LẠI KHÔNG PHẢI THIẾU, đã kiểm từng cái:
* audio_recorder_widget.py (T04) — repo KHÔNG có chức năng ghi âm nào.
* connector_settings_widget.py (T07) — UI Connector đã dời khỏi Cài đặt.
* sandbox_status_tab.py / mcp_history_tab.py (T08) — Hiệp đặt tên sandbox_tab
và mcp_tab, nội dung đủ.
R08: 14/14 task, 0 file thiếu thật sự.
756 test xanh. 24/24 checker qua.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""Nhập task từ file — R08-T11.
|
|
|
|
Tab thứ hai của hộp thoại tạo task: tải mẫu về, điền, rồi kéo file vào hoặc
|
|
chọn từ máy. Xem trước nội dung đọc được trước khi tạo, vì một file sai định
|
|
dạng có thể sinh ra hàng chục task rác.
|
|
|
|
Là mixin chứ không phải hộp thoại rời: nó dùng chung phần xem trước và nút
|
|
Xác nhận với tab tạo bằng AI, tách hẳn thì phải nhân đôi cả hai.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtWidgets import (
|
|
QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout,
|
|
QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QMessageBox,
|
|
QPlainTextEdit, QPushButton, QScrollArea, QSizePolicy, QStackedWidget,
|
|
QTabBar, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget,
|
|
)
|
|
from ...core import tasks as taskrepo
|
|
from ...core.projects import list_projects
|
|
from ...core.tasks import STATUSES, chain_error, duplicate_task, new_task
|
|
from ...core.worker import AgentWorker
|
|
from ...i18n import on_language_changed, tr
|
|
from ...state import AppContext
|
|
from ...theme import current_palette
|
|
from ...ui.calendar_view import CalendarView
|
|
from ...ui.icons import icon
|
|
from ...ui.osutil import open_path
|
|
from .kanban_board_widget import _DropZone
|
|
|
|
|
|
class TaskImportMixin:
|
|
"""Nhập task từ file. Trộn vào _AiCreateDialog."""
|
|
|
|
def _export_template(self) -> None:
|
|
from PySide6.QtWidgets import QFileDialog
|
|
|
|
from ...core.task_excel import export_template
|
|
|
|
path, _ = QFileDialog.getSaveFileName(
|
|
self, tr("schedtask.export_template_btn"),
|
|
"cowork_tasks_template.xlsx", "Excel (*.xlsx)")
|
|
if not path:
|
|
return
|
|
try:
|
|
export_template(path)
|
|
open_path(str(Path(path).parent))
|
|
except Exception as exc: # noqa: BLE001
|
|
QMessageBox.warning(self, tr("schedtask.tab_import"), str(exc))
|
|
def _pick_import_file(self) -> None:
|
|
from PySide6.QtWidgets import QFileDialog
|
|
|
|
from ...core.task_import import IMPORT_FILTER
|
|
|
|
path, _ = QFileDialog.getOpenFileName(
|
|
self, tr("schedtask.import_pick_btn"), "", IMPORT_FILTER)
|
|
if path:
|
|
self._load_import_file(path)
|
|
def _load_import_file(self, path: str) -> None:
|
|
from ...core.task_import import import_tasks
|
|
|
|
try:
|
|
self._planned = import_tasks(path)
|
|
except ValueError as exc:
|
|
self.import_preview.setPlainText(str(exc))
|
|
self.buttons.button(QDialogButtonBox.Ok).setEnabled(False)
|
|
return
|
|
by_id = {t["task_id"]: t["title"] for t in self._planned}
|
|
lines = []
|
|
for i, t in enumerate(self._planned, 1):
|
|
sched = t.get("schedule", {})
|
|
when = sched.get("run_at") if sched.get("enabled") else tr("schedtask.no_schedule")
|
|
deps = t.get("dependency", {}).get("depends_on") or []
|
|
dep_note = (" ← depends: " + ", ".join(by_id.get(d, "?") for d in deps)) if deps else ""
|
|
lines.append(f"{i}. [{t.get('task_type')}] {t.get('title')}\n"
|
|
f" {when} repeat={sched.get('repeat_type', 'none')}{dep_note}")
|
|
self.import_preview.setPlainText("\n\n".join(lines))
|
|
self.buttons.button(QDialogButtonBox.Ok).setEnabled(bool(self._planned))
|