Files
CST-agent/archive/desktop-tauri/src/components/BashPanel.tsx
T
WHCandCursor 5538ed3412
ci / tcc-check (push) Successful in 1m8s
ci / linux (aarch64, 0) (push) Failing after 1h0m3s
ci / linux (x86, 1) (push) Failing after 1h0m3s
ci / linux (x86_64, 1) (push) Failing after 1h0m2s
ci / windows (push) Failing after 50s
refactor: archive GUI clients; keep minimal TUI with hooks/API
Move Qt/Tauri/full Web SPA under archive/, ship hermit as CLI/TUI plus
libcstcore and cst-webui/1 HTTP+SSE (turn lifecycle hooks, capabilities).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 22:49:23 +08:00

93 lines
2.9 KiB
TypeScript

import { useMemo, useState } from "react";
import type { BashRun } from "@/lib/bridge";
type Filter = "recent" | "running" | "done" | "all";
type Props = {
runs: BashRun[];
onClose: () => void;
};
function fmtDur(r: BashRun): string {
const end = r.ended_at ?? Date.now();
const ms = Math.max(0, end - r.started_at);
if (ms < 1000) return `${ms}ms`;
if (ms < 60_000) return `${Math.round(ms / 1000)}秒`;
return `${Math.floor(ms / 60_000)}分${Math.round((ms % 60_000) / 1000)}秒`;
}
export function BashPanel({ runs, onClose }: Props) {
const [filter, setFilter] = useState<Filter>("recent");
const [openId, setOpenId] = useState<string | null>(null);
const list = useMemo(() => {
const sorted = [...runs].sort((a, b) => b.started_at - a.started_at);
if (filter === "all") return sorted;
if (filter === "running") return sorted.filter((r) => r.status === "running");
if (filter === "done")
return sorted.filter((r) => r.status !== "running");
return sorted.slice(0, 20);
}, [runs, filter]);
const running = runs.filter((r) => r.status === "running").length;
return (
<div className="work-panel bash-panel">
<div className="work-panel-head">
<strong>后台 Bash · {running} 运行中</strong>
<button type="button" onClick={onClose}>
关闭
</button>
</div>
<div className="bash-filters">
{(
[
["recent", "最近"],
["running", "进行中"],
["done", "已完成"],
["all", "全部"],
] as const
).map(([k, label]) => (
<button
key={k}
type="button"
className={filter === k ? "active" : ""}
onClick={() => setFilter(k)}
>
{label}
</button>
))}
</div>
{list.length === 0 ? (
<div className="work-empty">还没有 shell 工具记录。</div>
) : (
<ul className="bash-list">
{list.map((r) => (
<li key={r.id} className={`bash-row ${r.status}`}>
<button
type="button"
className="bash-row-main"
onClick={() => setOpenId(openId === r.id ? null : r.id)}
>
<span className="bash-status" aria-hidden>
{r.status === "ok"
? "✓"
: r.status === "running"
? "…"
: "✕"}
</span>
<code className="bash-cmd">$ {r.command}</code>
<span className="bash-dur">{fmtDur(r)}</span>
<span className="bash-chev">{openId === r.id ? "▾" : "▸"}</span>
</button>
{openId === r.id && r.output ? (
<pre className="bash-out">{r.output}</pre>
) : null}
</li>
))}
</ul>
)}
</div>
);
}