File size: 4,049 Bytes
c4ae742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import type { ReactNode } from "react";
import type { UiStatus, ViewId } from "./types";

export function Icon({ name, size = 14 }: { name: string; size?: number }) {
  const paths: Record<string, ReactNode> = {
    search: <><circle cx="7" cy="7" r="5" /><path d="M11 11l3 3" /></>,
    settings: <><circle cx="8" cy="8" r="2.5" /><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.5 3.5l1.4 1.4M11.1 11.1l1.4 1.4M3.5 12.5l1.4-1.4M11.1 4.9l1.4-1.4" /></>,
    play: <path d="M4 3l9 5-9 5z" fill="currentColor" />,
    stop: <rect x="4" y="4" width="8" height="8" rx="1.5" fill="currentColor" />,
    upload: <><path d="M8 11V3M5 6l3-3 3 3" /><path d="M2 11v2h12v-2" /></>,
    download: <><path d="M8 3v8M5 8l3 3 3-3" /><path d="M2 13h12" /></>,
    plus: <path d="M8 3v10M3 8h10" />,
    close: <path d="M3 3l10 10M13 3L3 13" />,
    retry: <><path d="M2 8a6 6 0 1 1 1.8 4.3" /><path d="M2 13v-3h3" /></>,
    trash: <><path d="M3 4h10" /><path d="M6 4V2.5h4V4" /><path d="M5 6l.5 7h5L11 6" /><path d="M7 7.5v4M9 7.5v4" /></>,
    file: <><path d="M3 2h6l4 4v8H3z" /><path d="M9 2v4h4" /></>,
    check: <path d="M3 8l3 3 7-7" />,
    diff: <><path d="M5 1v10M5 1l-2 2M5 1l2 2" /><path d="M11 15V5M11 15l-2-2M11 15l2-2" /></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {paths[name] || null}
    </svg>
  );
}

export function StatusPill({ state }: { state: UiStatus }) {
  const map: Record<UiStatus, string> = {
    idle: "Idle",
    queued: "Queued",
    running: "Running",
    done: "Done",
    failed: "Failed",
    stopped: "Stopped",
  };
  return (
    <span className={`pill ${state}`}>
      <span className="pill-dot"></span>
      {map[state]}
    </span>
  );
}

export function TopBar({
  view,
  onView,
  onSettings,
}: {
  view: ViewId;
  onView: (view: ViewId) => void;
  onSettings: () => void;
}) {
  const tabs: Array<{ id: ViewId; num: string; label: string }> = [
    { id: "polish", num: "01", label: "任务润色" },
    { id: "api", num: "02", label: "API 分组" },
    { id: "mcp", num: "03", label: "MCP 分组" },
  ];
  return (
    <header className="topbar">
      <div className="topbar-brand">
        <div className="brand-mark">T</div>
        <div className="brand-title">Task Polisher</div>
        <div className="brand-sub">local</div>
      </div>
      <nav className="view-tabs" aria-label="视图">
        {tabs.map((tab) => (
          <button key={tab.id} className={`view-tab ${view === tab.id ? "is-active" : ""}`} onClick={() => onView(tab.id)}>
            <span className="view-tab-num">{tab.num}</span>
            <span>{tab.label}</span>
          </button>
        ))}
      </nav>
      <div className="topbar-right">
        <button className="btn btn-ghost btn-sm" onClick={onSettings}>
          <Icon name="settings" size={12} /> 设置
        </button>
      </div>
    </header>
  );
}

export function StatusBar({
  view,
  total,
  done,
  failed,
  model,
  concurrency,
  message,
}: {
  view: ViewId;
  total: number;
  done: number;
  failed: number;
  model: string;
  concurrency: number;
  message: string;
}) {
  return (
    <footer className="statusbar">
      <span className="statusbar-item">
        <span className={`status-dot ${failed ? "warn" : "idle"}`}></span>
        <strong>{message || "idle"}</strong>
      </span>
      <span className="statusbar-item">view <strong>{view}</strong></span>
      <span className="statusbar-item">queue <strong>{done}/{total}</strong></span>
      {failed > 0 && <span className="statusbar-item"><span className="status-dot err"></span>failed <strong>{failed}</strong></span>}
      <span className="statusbar-spacer"></span>
      <span className="statusbar-item">model <strong>{model}</strong></span>
      <span className="statusbar-item">conc <strong>{concurrency}</strong></span>
      <span className="statusbar-item">storage <strong>local</strong></span>
    </footer>
  );
}