Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions runtime/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion runtime/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -37,6 +37,12 @@ export default function App() {
Settings
</NavLink>
</li>
<li>
<NavLink to="/feedback" className={({ isActive }) => (isActive ? "font-semibold" : "")}>
<MessageSquare className="inline w-4 h-4 mr-1" aria-hidden="true" />
Feedback
</NavLink>
</li>
</ul>
</nav>
</div>
Expand Down
2 changes: 2 additions & 0 deletions runtime/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -27,6 +28,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<Route path="/catalog" element={<CatalogPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="/doctor" element={<DoctorPage />} />
<Route path="/feedback" element={<FeedbackPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
Expand Down
195 changes: 195 additions & 0 deletions runtime/web/src/pages/FeedbackPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="max-w-2xl mx-auto p-6 text-center space-y-4">
<Check className="w-12 h-12 text-green-500 mx-auto" />
<h2 className="text-xl font-semibold">Thank you!</h2>
<p className="text-slate-600">Your feedback has been saved. We review every submission.</p>
<button onClick={() => { setSent(false); setTitle(""); setDesc(""); setModule(""); }} className="text-blue-600 hover:underline text-sm">
Submit another
</button>
</div>
);
}

return (
<div className="max-w-2xl mx-auto p-6 space-y-6">
<h2 className="text-xl font-semibold flex items-center gap-2">
<MessageSquare className="w-5 h-5" /> Feedback
</h2>

{error && (
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<AlertCircle className="w-4 h-4" /> {error}
</div>
)}

<form onSubmit={submit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<section className="space-y-2">
<label className="text-sm font-medium text-slate-600">Type</label>
<div className="grid grid-cols-2 gap-1.5">
{TYPES.map((t) => (
<button
key={t}
type="button"
onClick={() => setType(t)}
className={`text-left px-3 py-1.5 rounded-lg border text-xs transition
${type === t ? "border-blue-500 bg-blue-50 ring-1 ring-blue-500" : "border-slate-200 hover:border-slate-300"}`}
>
{t}
</button>
))}
</div>
</section>

<section className="space-y-2">
<label className="text-sm font-medium text-slate-600">Module</label>
<select
value={module}
onChange={(e) => setModule(e.target.value)}
className="w-full px-3 py-2 border rounded-lg text-sm"
required
>
<option value="" disabled>Select module...</option>
{MODULES.map((m) => (
<option key={m} value={m}>{m}</option>
))}
</select>
</section>
</div>

<section className="space-y-2">
<label className="text-sm font-medium text-slate-600">Title</label>
<input
type="text"
value={title}
onChange={(e) => 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
/>
</section>

<section className="space-y-2">
<label className="text-sm font-medium text-slate-600">Description</label>
<textarea
value={desc}
onChange={(e) => setDesc(e.target.value)}
placeholder="Steps to reproduce, expected vs actual behavior, screenshots..."
rows={5}
className="w-full px-3 py-2 border rounded-lg text-sm resize-y"
required
/>
</section>

<section className="space-y-2">
<label className="text-sm font-medium text-slate-600">
Email <span className="text-slate-400 font-normal">(optional — we'll reply if needed)</span>
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
className="w-full px-3 py-2 border rounded-lg text-sm"
/>
</section>

<button
type="submit"
className="flex items-center gap-2 px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm"
>
<Send className="w-4 h-4" /> Submit Feedback
</button>
</form>

<p className="text-xs text-slate-400">
Feedback is stored locally and reviewed regularly.
For urgent issues, open a{" "}
<a href="https://github.com/Wool-xing/Test-Agent/issues/new" target="_blank" rel="noopener" className="text-blue-600 hover:underline">
GitHub Issue
</a>.
</p>
</div>
);
}
Loading