@agentprojectcontext/apx 1.42.2 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/config/index.js +2 -0
- package/src/core/config/redact.js +2 -0
- package/src/core/desktop/process.js +126 -0
- package/src/core/voice/stt-hardware.js +87 -0
- package/src/core/voice/stt-models.js +97 -0
- package/src/core/voice/transcription.js +147 -16
- package/src/host/daemon/api/desktop.js +54 -8
- package/src/host/daemon/api/transcribe.js +40 -1
- package/src/host/daemon/whisper-server.js +18 -8
- package/src/host/daemon/whisper-server.py +71 -44
- package/src/interfaces/cli/commands/desktop.js +13 -68
- package/src/interfaces/desktop/main.js +32 -4
- package/src/interfaces/desktop/renderer.js +26 -5
- package/src/interfaces/web/dist/assets/index-BAKk7d_M.css +1 -0
- package/src/interfaces/web/dist/assets/index-Cjj_d3SA.js +656 -0
- package/src/interfaces/web/dist/assets/index-Cjj_d3SA.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/Pager.tsx +88 -0
- package/src/interfaces/web/src/components/ShortcutInput.tsx +156 -0
- package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +101 -5
- package/src/interfaces/web/src/i18n/en.ts +33 -2
- package/src/interfaces/web/src/i18n/es.ts +33 -2
- package/src/interfaces/web/src/lib/api/desktop.ts +28 -0
- package/src/interfaces/web/src/lib/api/voice.ts +26 -2
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +4 -1
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +4 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +55 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +98 -36
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +4 -1
- package/src/interfaces/web/dist/assets/index-BReF4_xV.js +0 -646
- package/src/interfaces/web/dist/assets/index-BReF4_xV.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-wrEbTJbc.css +0 -1
|
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|
|
3
3
|
import useSWR from "swr";
|
|
4
4
|
import { Tasks } from "../../lib/api";
|
|
5
5
|
import { Section } from "../../components/Section";
|
|
6
|
+
import { Pager, usePaged } from "../../components/Pager";
|
|
6
7
|
import { Badge, Button, Empty, Loading } from "../../components/ui";
|
|
7
8
|
import { t } from "../../i18n";
|
|
8
9
|
|
|
@@ -11,6 +12,7 @@ export function GlobalTasksTab() {
|
|
|
11
12
|
const navigate = useNavigate();
|
|
12
13
|
const [state, setState] = useState<"open" | "done" | "dropped" | "all">("open");
|
|
13
14
|
const list = useSWR(`/tasks?state=${state}`, () => Tasks.global(state));
|
|
15
|
+
const paged = usePaged(list.data || [], state);
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
18
|
<Section
|
|
@@ -27,7 +29,7 @@ export function GlobalTasksTab() {
|
|
|
27
29
|
{list.isLoading && <Loading />}
|
|
28
30
|
{!list.isLoading && (list.data?.length ?? 0) === 0 && <Empty>{t("project.global_tasks.empty")}</Empty>}
|
|
29
31
|
<ul className="space-y-2 text-sm">
|
|
30
|
-
{
|
|
32
|
+
{paged.slice.map((task) => (
|
|
31
33
|
<li key={`${task.project_id}-${task.id}`} className="flex items-start gap-3 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
32
34
|
<button
|
|
33
35
|
type="button"
|
|
@@ -48,6 +50,7 @@ export function GlobalTasksTab() {
|
|
|
48
50
|
</li>
|
|
49
51
|
))}
|
|
50
52
|
</ul>
|
|
53
|
+
<Pager page={paged.page} pageCount={paged.pageCount} total={paged.total} start={paged.start} end={paged.end} pageSize={paged.pageSize} onPage={paged.setPage} onPageSize={paged.setPageSize} />
|
|
51
54
|
</Section>
|
|
52
55
|
);
|
|
53
56
|
}
|
|
@@ -3,6 +3,7 @@ import useSWR from "swr";
|
|
|
3
3
|
import { RefreshCw } from "lucide-react";
|
|
4
4
|
import { Sessions } from "../../lib/api";
|
|
5
5
|
import { Section } from "../../components/Section";
|
|
6
|
+
import { Pager, usePaged } from "../../components/Pager";
|
|
6
7
|
import { Badge, Button, Empty, Loading } from "../../components/ui";
|
|
7
8
|
import { UiSelect } from "../../components/UiSelect";
|
|
8
9
|
import { t } from "../../i18n";
|
|
@@ -15,6 +16,7 @@ export function SessionsTab() {
|
|
|
15
16
|
const [engine, setEngine] = useState("");
|
|
16
17
|
const list = useSWR(`/sessions?engine=${engine}`, () => Sessions.global(engine || undefined));
|
|
17
18
|
const rows = list.data?.sessions || [];
|
|
19
|
+
const paged = usePaged(rows, engine);
|
|
18
20
|
|
|
19
21
|
return (
|
|
20
22
|
<Section
|
|
@@ -42,7 +44,7 @@ export function SessionsTab() {
|
|
|
42
44
|
{list.error && <Empty>{t("base.sessions_error", { msg: (list.error as Error).message })}</Empty>}
|
|
43
45
|
{!list.isLoading && !list.error && rows.length === 0 && <Empty>{t("base.sessions_empty")}</Empty>}
|
|
44
46
|
<ul className="space-y-1 text-sm">
|
|
45
|
-
{
|
|
47
|
+
{paged.slice.map((s, i) => (
|
|
46
48
|
<li key={`${s.engine}-${s.id}-${i}`} className="flex items-center gap-3 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
47
49
|
<Badge tone={ENGINE_TONE[s.engine] || "muted"}>{s.engine}</Badge>
|
|
48
50
|
<div className="min-w-0 flex-1">
|
|
@@ -53,6 +55,7 @@ export function SessionsTab() {
|
|
|
53
55
|
</li>
|
|
54
56
|
))}
|
|
55
57
|
</ul>
|
|
58
|
+
<Pager page={paged.page} pageCount={paged.pageCount} total={paged.total} start={paged.start} end={paged.end} pageSize={paged.pageSize} onPage={paged.setPage} onPageSize={paged.setPageSize} />
|
|
56
59
|
</Section>
|
|
57
60
|
);
|
|
58
61
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import useSWR from "swr";
|
|
2
|
-
import { RefreshCw } from "lucide-react";
|
|
2
|
+
import { RefreshCw, Construction } from "lucide-react";
|
|
3
3
|
import { Deck } from "../../lib/api/deck";
|
|
4
4
|
import { Section } from "../../components/Section";
|
|
5
5
|
import { Button, Empty, Loading } from "../../components/ui";
|
|
@@ -21,7 +21,17 @@ export function DeckScreen() {
|
|
|
21
21
|
{ refreshInterval: 30_000 }
|
|
22
22
|
);
|
|
23
23
|
|
|
24
|
+
// ── Deck is NOT released yet ───────────────────────────────────────────
|
|
25
|
+
// The whole module is shown behind a non-dismissable "coming soon" modal
|
|
26
|
+
// (see the overlay at the bottom of this component) and the UI underneath is
|
|
27
|
+
// made inert. As defense-in-depth, the real widget mutation is disabled here
|
|
28
|
+
// too: if someone forces the modal out of the DOM, toggling a widget does
|
|
29
|
+
// NOTHING — no request ever hits the daemon, so nothing breaks.
|
|
30
|
+
// 👉 Re-enable the body below (remove this guard) when Deck ships in a stable
|
|
31
|
+
// release. The original implementation is kept intact, just unreachable.
|
|
24
32
|
const handleToggle = async (widgetId: string, enabled: boolean) => {
|
|
33
|
+
return; // no-op while Deck is pre-release — see note above.
|
|
34
|
+
// eslint-disable-next-line no-unreachable
|
|
25
35
|
try {
|
|
26
36
|
await Deck.setWidget(widgetId, { enabled });
|
|
27
37
|
// Optimistically update local data so the switch flips immediately.
|
|
@@ -58,7 +68,11 @@ export function DeckScreen() {
|
|
|
58
68
|
// Re-validate after a short delay to get the server's persisted state.
|
|
59
69
|
setTimeout(() => mutate(), 800);
|
|
60
70
|
} catch (e: unknown) {
|
|
61
|
-
|
|
71
|
+
// `e` reads as `unknown` here because the early `return` above makes this
|
|
72
|
+
// catch unreachable while Deck is pre-release — TS skips control-flow
|
|
73
|
+
// narrowing in dead code. Cast so the kept-for-later body still
|
|
74
|
+
// typechecks; behaviour is unchanged (this block never runs today).
|
|
75
|
+
const msg = e instanceof Error ? (e as Error).message : t("modules_ui.deck_save_error");
|
|
62
76
|
toast.error(msg);
|
|
63
77
|
}
|
|
64
78
|
};
|
|
@@ -77,7 +91,18 @@ export function DeckScreen() {
|
|
|
77
91
|
const enabledCount = externalWidgets.filter((w) => w.user_enabled === true).length;
|
|
78
92
|
|
|
79
93
|
return (
|
|
80
|
-
<div className="
|
|
94
|
+
<div className="relative min-h-full" data-testid="screen-deck">
|
|
95
|
+
{/* ── Underlying Deck UI ──────────────────────────────────────────────
|
|
96
|
+
Shown for context only while Deck is pre-release. It is made fully
|
|
97
|
+
inert (no focus, no pointer events) and blurred behind the overlay
|
|
98
|
+
below. All real actions are also neutralized in code (see
|
|
99
|
+
handleToggle), so nothing runs even if this layer is re-enabled by
|
|
100
|
+
hand. Remove the `inert`/blur wrapper when Deck ships. */}
|
|
101
|
+
<div
|
|
102
|
+
className="mx-auto max-w-4xl space-y-6 p-6 pointer-events-none select-none blur-[2px] opacity-60"
|
|
103
|
+
aria-hidden
|
|
104
|
+
inert
|
|
105
|
+
>
|
|
81
106
|
{/* Daemon info card */}
|
|
82
107
|
{data && <DaemonCard manifest={data} />}
|
|
83
108
|
|
|
@@ -160,5 +185,32 @@ export function DeckScreen() {
|
|
|
160
185
|
</Section>
|
|
161
186
|
)}
|
|
162
187
|
</div>
|
|
188
|
+
|
|
189
|
+
{/* ── "Coming soon" overlay ───────────────────────────────────────────
|
|
190
|
+
Non-dismissable: there is no close button and clicking the backdrop
|
|
191
|
+
does nothing. It floats over the inert Deck UI above. Remove this
|
|
192
|
+
whole block (and the inert wrapper / handleToggle guard) when Deck is
|
|
193
|
+
released. */}
|
|
194
|
+
<div
|
|
195
|
+
className="absolute inset-0 z-10 flex items-center justify-center p-6 backdrop-blur-[1px]"
|
|
196
|
+
role="dialog"
|
|
197
|
+
aria-modal="true"
|
|
198
|
+
aria-labelledby="deck-coming-soon-title"
|
|
199
|
+
data-testid="deck-coming-soon"
|
|
200
|
+
>
|
|
201
|
+
<div className="w-full max-w-md rounded-2xl border border-border bg-card/95 p-8 text-center shadow-2xl">
|
|
202
|
+
<div className="mx-auto mb-4 flex size-12 items-center justify-center rounded-full bg-muted">
|
|
203
|
+
<Construction className="size-6 text-muted-fg" />
|
|
204
|
+
</div>
|
|
205
|
+
<span className="inline-block rounded-full bg-muted px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-fg">
|
|
206
|
+
{t("deck_screen.preview_badge")}
|
|
207
|
+
</span>
|
|
208
|
+
<h2 id="deck-coming-soon-title" className="mt-3 text-lg font-semibold">
|
|
209
|
+
{t("deck_screen.preview_title")}
|
|
210
|
+
</h2>
|
|
211
|
+
<p className="mt-2 text-sm text-muted-fg">{t("deck_screen.preview_body")}</p>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
163
215
|
);
|
|
164
216
|
}
|
|
@@ -2,8 +2,9 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
2
2
|
import { Link } from "react-router-dom";
|
|
3
3
|
import useSWR from "swr";
|
|
4
4
|
import { Section, Kbd, StatusDot } from "../../components/Section";
|
|
5
|
-
import { Button, Field,
|
|
5
|
+
import { Button, Field, Switch, Loading, Empty } from "../../components/ui";
|
|
6
6
|
import { UiSelect } from "../../components/UiSelect";
|
|
7
|
+
import { ShortcutInput } from "../../components/ShortcutInput";
|
|
7
8
|
import { useToast } from "../../components/Toast";
|
|
8
9
|
import { useGlobalConfig } from "../../hooks/useGlobalConfig";
|
|
9
10
|
import { Desktop, fetchDesktopMessages, type GlobalMessage } from "../../lib/api/desktop";
|
|
@@ -16,8 +17,9 @@ const positionOpts = () => [
|
|
|
16
17
|
{ value: "right", label: t("modules_ui.desktop_pos_right") },
|
|
17
18
|
];
|
|
18
19
|
const themeOpts = () => [
|
|
19
|
-
{ value: "
|
|
20
|
-
{ value: "
|
|
20
|
+
{ value: "system", label: t("modules_ui.desktop_theme_system") },
|
|
21
|
+
{ value: "light", label: t("modules_ui.desktop_theme_light") },
|
|
22
|
+
{ value: "dark", label: t("modules_ui.desktop_theme_dark") },
|
|
21
23
|
];
|
|
22
24
|
|
|
23
25
|
// Desktop module — manage the floating voice window (the Electron app launched
|
|
@@ -32,14 +34,16 @@ export function DesktopScreen() {
|
|
|
32
34
|
const cfgView = config as unknown as {
|
|
33
35
|
desktop?: {
|
|
34
36
|
shortcut?: string; enabled?: boolean;
|
|
35
|
-
theme?: "light" | "dark";
|
|
37
|
+
theme?: "light" | "dark" | "system";
|
|
36
38
|
position?: "left" | "center" | "right";
|
|
37
39
|
};
|
|
38
40
|
overlay?: { shortcut?: string }; // legacy fallback
|
|
39
41
|
};
|
|
40
42
|
const savedShortcut = cfgView.desktop?.shortcut || cfgView.overlay?.shortcut || DEFAULT_SHORTCUT;
|
|
41
43
|
const enabled = cfgView.desktop?.enabled !== false;
|
|
42
|
-
|
|
44
|
+
// Default to "system" so the window follows the OS appearance until the
|
|
45
|
+
// user explicitly pins light/dark.
|
|
46
|
+
const theme = cfgView.desktop?.theme || "system";
|
|
43
47
|
const position = cfgView.desktop?.position || "right";
|
|
44
48
|
|
|
45
49
|
const { data: status, isLoading: stLoading, mutate: mutateStatus } = useSWR(
|
|
@@ -63,8 +67,35 @@ export function DesktopScreen() {
|
|
|
63
67
|
const [shortcut, setShortcut] = useState(savedShortcut);
|
|
64
68
|
const [busy, setBusy] = useState(false);
|
|
65
69
|
const [autostartBusy, setAutostartBusy] = useState(false);
|
|
70
|
+
// Which lifecycle action (start/stop/restart) is in flight — drives the
|
|
71
|
+
// per-button spinner and disables its siblings while one runs.
|
|
72
|
+
const [lifeAction, setLifeAction] = useState<"start" | "stop" | "restart" | null>(null);
|
|
66
73
|
useEffect(() => setShortcut(savedShortcut), [savedShortcut]);
|
|
67
74
|
|
|
75
|
+
// Start/Stop launch or kill the Electron window (daemon spawns/SIGTERMs it);
|
|
76
|
+
// Restart tells a live window to reload + re-read config (theme, position,
|
|
77
|
+
// shortcut) — the "apply now" the static status-poll never did. All three
|
|
78
|
+
// re-poll status shortly after so the dot + buttons settle.
|
|
79
|
+
const runLifecycle = async (action: "start" | "stop" | "restart", fn: () => Promise<void>) => {
|
|
80
|
+
setLifeAction(action);
|
|
81
|
+
try { await fn(); }
|
|
82
|
+
catch (e) { toast.error((e as Error).message); }
|
|
83
|
+
finally { setLifeAction(null); setTimeout(() => mutateStatus(), 1200); }
|
|
84
|
+
};
|
|
85
|
+
const startDesktop = () => runLifecycle("start", async () => {
|
|
86
|
+
const r = await Desktop.start();
|
|
87
|
+
toast.success(r.already ? t("modules_ui.desktop_start_already") : t("modules_ui.desktop_start_done"));
|
|
88
|
+
});
|
|
89
|
+
const stopDesktop = () => runLifecycle("stop", async () => {
|
|
90
|
+
const r = await Desktop.stop();
|
|
91
|
+
toast.success(r.stopped ? t("modules_ui.desktop_stop_done") : t("modules_ui.desktop_stop_none"));
|
|
92
|
+
});
|
|
93
|
+
const restartDesktop = () => runLifecycle("restart", async () => {
|
|
94
|
+
const r = await Desktop.restart();
|
|
95
|
+
if (r.reloaded > 0) toast.success(t("modules_ui.desktop_restart_done"));
|
|
96
|
+
else toast.info(t("modules_ui.desktop_restart_none"));
|
|
97
|
+
});
|
|
98
|
+
|
|
68
99
|
const saveShortcut = async () => {
|
|
69
100
|
const next = shortcut.trim();
|
|
70
101
|
if (!next || next === savedShortcut) return;
|
|
@@ -101,23 +132,58 @@ export function DesktopScreen() {
|
|
|
101
132
|
<div className="grid gap-6 xl:grid-cols-[1fr_1fr]">
|
|
102
133
|
{/* ── LEFT: configuration + status ─────────────────────────────── */}
|
|
103
134
|
<div className="space-y-6">
|
|
104
|
-
<Section
|
|
135
|
+
<Section
|
|
136
|
+
title={t("desktop_screen.status_title")}
|
|
137
|
+
description={t("modules_ui.desktop_status_desc")}
|
|
138
|
+
action={
|
|
139
|
+
<div className="flex items-center gap-2">
|
|
140
|
+
<Button
|
|
141
|
+
variant="primary"
|
|
142
|
+
size="sm"
|
|
143
|
+
onClick={startDesktop}
|
|
144
|
+
loading={lifeAction === "start"}
|
|
145
|
+
disabled={running || (lifeAction !== null && lifeAction !== "start")}
|
|
146
|
+
>
|
|
147
|
+
{t("modules_ui.desktop_start")}
|
|
148
|
+
</Button>
|
|
149
|
+
<Button
|
|
150
|
+
variant="secondary"
|
|
151
|
+
size="sm"
|
|
152
|
+
onClick={stopDesktop}
|
|
153
|
+
loading={lifeAction === "stop"}
|
|
154
|
+
disabled={!running || (lifeAction !== null && lifeAction !== "stop")}
|
|
155
|
+
>
|
|
156
|
+
{t("modules_ui.desktop_stop")}
|
|
157
|
+
</Button>
|
|
158
|
+
<Button
|
|
159
|
+
variant="secondary"
|
|
160
|
+
size="sm"
|
|
161
|
+
onClick={restartDesktop}
|
|
162
|
+
loading={lifeAction === "restart"}
|
|
163
|
+
disabled={!running || (lifeAction !== null && lifeAction !== "restart")}
|
|
164
|
+
title={t("modules_ui.desktop_restart_hint")}
|
|
165
|
+
>
|
|
166
|
+
{t("modules_ui.desktop_restart")}
|
|
167
|
+
</Button>
|
|
168
|
+
</div>
|
|
169
|
+
}
|
|
170
|
+
>
|
|
105
171
|
{stLoading ? <Loading /> : (
|
|
106
|
-
<div className="flex items-center gap-2 text-sm">
|
|
172
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
|
|
107
173
|
<StatusDot ok={running} />
|
|
108
174
|
<span className="font-medium">{running ? t("modules_ui.desktop_running") : t("modules_ui.desktop_stopped")}</span>
|
|
109
175
|
<button
|
|
110
176
|
type="button"
|
|
111
177
|
onClick={() => mutateStatus()}
|
|
112
|
-
className="
|
|
178
|
+
className="text-xs text-muted-fg underline-offset-2 hover:underline"
|
|
113
179
|
>
|
|
114
180
|
{t("modules_ui.desktop_refresh")}
|
|
115
181
|
</button>
|
|
182
|
+
<span className="text-xs text-muted-fg">
|
|
183
|
+
({t("modules_ui.desktop_from_terminal")} <Kbd>apx desktop start</Kbd> · <Kbd>apx desktop --debug</Kbd>)
|
|
184
|
+
</span>
|
|
116
185
|
</div>
|
|
117
186
|
)}
|
|
118
|
-
<p className="mt-3 text-xs text-muted-fg">
|
|
119
|
-
{t("modules_ui.desktop_from_terminal")} <Kbd>apx desktop start</Kbd> · <Kbd>apx desktop --debug</Kbd>
|
|
120
|
-
</p>
|
|
121
187
|
</Section>
|
|
122
188
|
|
|
123
189
|
<Section
|
|
@@ -142,31 +208,27 @@ export function DesktopScreen() {
|
|
|
142
208
|
description={t("modules_ui.desktop_shortcut_desc")}
|
|
143
209
|
>
|
|
144
210
|
{cfgLoading ? <Loading /> : (
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
>
|
|
167
|
-
{t("common.save")}
|
|
168
|
-
</Button>
|
|
169
|
-
</div>
|
|
211
|
+
<Field
|
|
212
|
+
label={t("modules_ui.desktop_accelerator")}
|
|
213
|
+
hint={t("modules_ui.desktop_accelerator_hint")}
|
|
214
|
+
>
|
|
215
|
+
<ShortcutInput
|
|
216
|
+
value={shortcut}
|
|
217
|
+
onChange={setShortcut}
|
|
218
|
+
disabled={busy}
|
|
219
|
+
trailing={
|
|
220
|
+
<Button
|
|
221
|
+
variant="primary"
|
|
222
|
+
size="sm"
|
|
223
|
+
onClick={saveShortcut}
|
|
224
|
+
loading={busy}
|
|
225
|
+
disabled={!shortcut.trim() || shortcut.trim() === savedShortcut}
|
|
226
|
+
>
|
|
227
|
+
{t("common.save")}
|
|
228
|
+
</Button>
|
|
229
|
+
}
|
|
230
|
+
/>
|
|
231
|
+
</Field>
|
|
170
232
|
)}
|
|
171
233
|
</Section>
|
|
172
234
|
|
|
@@ -3,6 +3,7 @@ import useSWR from "swr";
|
|
|
3
3
|
import { Check, Plus, RotateCcw, Trash2 } from "lucide-react";
|
|
4
4
|
import { Tasks } from "../../lib/api";
|
|
5
5
|
import { Section } from "../../components/Section";
|
|
6
|
+
import { Pager, usePaged } from "../../components/Pager";
|
|
6
7
|
import { Badge, Button, Empty, Field, Input, Loading } from "../../components/ui";
|
|
7
8
|
import { useToast } from "../../components/Toast";
|
|
8
9
|
import { t } from "../../i18n";
|
|
@@ -17,6 +18,7 @@ export function TasksTab({ pid }: { pid: string }) {
|
|
|
17
18
|
() => Tasks.list(pid, state),
|
|
18
19
|
{ dedupingInterval: 0, revalidateOnFocus: true },
|
|
19
20
|
);
|
|
21
|
+
const paged = usePaged(list.data || [], state);
|
|
20
22
|
const [draft, setDraft] = useState("");
|
|
21
23
|
const [busy, setBusy] = useState(false);
|
|
22
24
|
|
|
@@ -79,7 +81,7 @@ export function TasksTab({ pid }: { pid: string }) {
|
|
|
79
81
|
)}
|
|
80
82
|
|
|
81
83
|
<ul className="space-y-2 text-sm" data-testid="task-list">
|
|
82
|
-
{
|
|
84
|
+
{paged.slice.map((task) => (
|
|
83
85
|
<li key={task.id} data-testid={`task-${task.id}`} className="flex items-start gap-3 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
84
86
|
<span className="mt-0.5 font-mono text-[10px] text-muted-fg">{task.id}</span>
|
|
85
87
|
<div className="flex-1">
|
|
@@ -111,6 +113,7 @@ export function TasksTab({ pid }: { pid: string }) {
|
|
|
111
113
|
</li>
|
|
112
114
|
))}
|
|
113
115
|
</ul>
|
|
116
|
+
<Pager page={paged.page} pageCount={paged.pageCount} total={paged.total} start={paged.start} end={paged.end} pageSize={paged.pageSize} onPage={paged.setPage} onPageSize={paged.setPageSize} />
|
|
114
117
|
</Section>
|
|
115
118
|
);
|
|
116
119
|
}
|