From d7260dcdf587d6f6e8400ba51ff9975cbeb23989 Mon Sep 17 00:00:00 2001 From: xiaoxing0135 <706015750@qq.com> Date: Sun, 17 May 2026 00:26:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20Feedback=20module=20=E2=80=94=20s?= =?UTF-8?q?tructured=20user=20feedback=20with=20GUI=20+=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FeedbackPage.tsx: new page with type/module/title/description/email form - 4 feedback types: Bug Report, Feature Request, Question, Feedback - 12 module categories for precise issue routing - Saves to localStorage; POST to /feedback in desktop app - Thank-you screen after submission - API: POST /feedback endpoint saves to workspace/feedback/*.json - Navigation: Feedback link in App.tsx header - Route registered in main.tsx --- runtime/api/main.py | 20 +++ runtime/web/src/App.tsx | 8 +- runtime/web/src/main.tsx | 2 + runtime/web/src/pages/FeedbackPage.tsx | 195 +++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 runtime/web/src/pages/FeedbackPage.tsx diff --git a/runtime/api/main.py b/runtime/api/main.py index 4697242..1783969 100644 --- a/runtime/api/main.py +++ b/runtime/api/main.py @@ -14,6 +14,7 @@ from runtime.api.deps import Kernel from runtime.api.models import CatalogResponse, RunCreateText, RunCreated, RunStatus as RunStatusModel from runtime.api.parsers import parse_path, parse_text, parse_url +from runtime.config.settings import get_settings app = FastAPI(title="Test-Agent Runtime", version=__version__) app.add_middleware( @@ -128,6 +129,25 @@ def report(run_id: str) -> JSONResponse: return JSONResponse(res) +@app.post("/feedback") +def submit_feedback(payload: dict) -> dict: + """Accept user feedback from desktop/web UI. Logs to workspace/feedback/.""" + import json as _json + from datetime import datetime, timezone + + fb_dir = get_settings().workspace_dir / "feedback" + fb_dir.mkdir(parents=True, exist_ok=True) + entry = { + **payload, + "received_at": datetime.now(timezone.utc).isoformat(), + } + ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") + fname = fb_dir / f"feedback-{ts}.json" + fname.write_text(_json.dumps(entry, indent=2, ensure_ascii=False), encoding="utf-8") + logger.info("feedback saved: {}", fname) + return {"status": "ok", "saved_to": str(fname)} + + def _run_in_background(run_id: str, decision) -> None: try: summary = _kernel.execute_sync(run_id, decision) diff --git a/runtime/web/src/App.tsx b/runtime/web/src/App.tsx index c2bb3e4..650797d 100644 --- a/runtime/web/src/App.tsx +++ b/runtime/web/src/App.tsx @@ -1,5 +1,5 @@ import { Outlet, NavLink } from "react-router-dom"; -import { Beaker, Upload, BookOpen, Settings, Stethoscope } from "lucide-react"; +import { Beaker, Upload, BookOpen, Settings, Stethoscope, MessageSquare } from "lucide-react"; export default function App() { return ( @@ -37,6 +37,12 @@ export default function App() { Settings +
  • + (isActive ? "font-semibold" : "")}> + +
  • diff --git a/runtime/web/src/main.tsx b/runtime/web/src/main.tsx index 7c9209d..9b5218e 100644 --- a/runtime/web/src/main.tsx +++ b/runtime/web/src/main.tsx @@ -9,6 +9,7 @@ import ReportPage from "./pages/ReportPage"; import CatalogPage from "./pages/CatalogPage"; import SettingsPage from "./pages/SettingsPage"; import DoctorPage from "./pages/DoctorPage"; +import FeedbackPage from "./pages/FeedbackPage"; import "./index.css"; const queryClient = new QueryClient({ @@ -27,6 +28,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render( } /> } /> } /> + } /> } /> diff --git a/runtime/web/src/pages/FeedbackPage.tsx b/runtime/web/src/pages/FeedbackPage.tsx new file mode 100644 index 0000000..4c4955c --- /dev/null +++ b/runtime/web/src/pages/FeedbackPage.tsx @@ -0,0 +1,195 @@ +import { useState, FormEvent } from "react"; +import { MessageSquare, Send, Check, AlertCircle } from "lucide-react"; + +const MODULES = [ + "Upload / Run", + "Report / Results", + "Catalog / Skills", + "Settings / Config", + "Doctor / Check", + "CLI / tagent", + "Desktop App", + "Install / Setup", + "Documentation", + "Expert (specify)", + "Skill (specify)", + "Other", +]; + +const TYPES = ["Bug Report", "Feature Request", "Question", "Feedback"]; + +interface FeedbackEntry { + id: string; + type: string; + module: string; + title: string; + description: string; + email: string; + timestamp: string; +} + +function saveFeedback(entry: FeedbackEntry) { + const existing = JSON.parse(localStorage.getItem("tagent_feedback") || "[]"); + existing.push(entry); + localStorage.setItem("tagent_feedback", JSON.stringify(existing)); +} + +export default function FeedbackPage() { + const [type, setType] = useState("Bug Report"); + const [module, setModule] = useState(""); + const [title, setTitle] = useState(""); + const [desc, setDesc] = useState(""); + const [email, setEmail] = useState(""); + const [sent, setSent] = useState(false); + const [error, setError] = useState(""); + + const submit = (e: FormEvent) => { + e.preventDefault(); + if (!module || !title || !desc) { + setError("Please fill in module, title, and description."); + return; + } + + const entry: FeedbackEntry = { + id: `fb-${Date.now()}`, + type, + module, + title, + description: desc, + email, + timestamp: new Date().toISOString(), + }; + + saveFeedback(entry); + + // Also try to submit to GitHub Issues if in desktop app + if ((window as any).electronAPI?.isElectron) { + try { + const body = `## ${type}: ${title}\n\n**Module**: ${module}\n**Email**: ${email || "N/A"}\n\n### Description\n${desc}\n\n---\n*Submitted via Test-Agent Desktop v1.32.0*`; + fetch("http://localhost:8800/feedback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type, module, title, body }), + }).catch(() => {}); + } catch {} + } + + setSent(true); + setError(""); + }; + + if (sent) { + return ( +
    + +

    Thank you!

    +

    Your feedback has been saved. We review every submission.

    + +
    + ); + } + + return ( +
    +

    + Feedback +

    + + {error && ( +
    + {error} +
    + )} + +
    +
    +
    + +
    + {TYPES.map((t) => ( + + ))} +
    +
    + +
    + + +
    +
    + +
    + + setTitle(e.target.value)} + placeholder="Brief summary of the issue or suggestion" + className="w-full px-3 py-2 border rounded-lg text-sm" + required + /> +
    + +
    + +