@agentprojectcontext/apx 1.43.0 → 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/interfaces/web/dist/assets/index-BAKk7d_M.css +1 -0
- package/src/interfaces/web/dist/assets/{index-B0nTYflm.js → index-Cjj_d3SA.js} +116 -111
- 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/i18n/en.ts +5 -0
- package/src/interfaces/web/src/i18n/es.ts +5 -0
- 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/project/TasksTab.tsx +4 -1
- package/src/interfaces/web/dist/assets/index-B0nTYflm.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-C22PmKCD.css +0 -1
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-Cjj_d3SA.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BAKk7d_M.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
25
25
|
<div id="root"></div>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
3
|
+
import { Button } from "./ui";
|
|
4
|
+
import { UiSelect } from "./UiSelect";
|
|
5
|
+
import { t } from "../i18n";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
8
|
+
export const PAGE_SIZES = [10, 20, 50, 100];
|
|
9
|
+
|
|
10
|
+
// Client-side pagination over an already-fetched array. The list endpoints
|
|
11
|
+
// return the full set (sessions/tasks are bounded), so we page in the browser
|
|
12
|
+
// rather than round-trip the daemon. Pass `resetKey` (e.g. the active filter)
|
|
13
|
+
// to jump back to page 1 whenever the source set changes; the window is also
|
|
14
|
+
// clamped so a shrinking list never strands the user on an empty page.
|
|
15
|
+
export function usePaged<T>(items: T[], resetKey?: unknown, initialPageSize = DEFAULT_PAGE_SIZE) {
|
|
16
|
+
const [page, setPage] = useState(1);
|
|
17
|
+
const [pageSize, setPageSize] = useState(initialPageSize);
|
|
18
|
+
|
|
19
|
+
useEffect(() => { setPage(1); }, [resetKey]);
|
|
20
|
+
|
|
21
|
+
const pageCount = Math.max(1, Math.ceil(items.length / pageSize));
|
|
22
|
+
const safePage = Math.min(page, pageCount);
|
|
23
|
+
useEffect(() => { if (page !== safePage) setPage(safePage); }, [page, safePage]);
|
|
24
|
+
|
|
25
|
+
const start = (safePage - 1) * pageSize;
|
|
26
|
+
const end = Math.min(start + pageSize, items.length);
|
|
27
|
+
return {
|
|
28
|
+
slice: items.slice(start, end),
|
|
29
|
+
page: safePage,
|
|
30
|
+
pageCount,
|
|
31
|
+
total: items.length,
|
|
32
|
+
start,
|
|
33
|
+
end,
|
|
34
|
+
pageSize,
|
|
35
|
+
setPage,
|
|
36
|
+
// Changing the page size keeps things predictable by returning to page 1.
|
|
37
|
+
setPageSize: (n: number) => { setPageSize(n); setPage(1); },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function Pager({
|
|
42
|
+
page,
|
|
43
|
+
pageCount,
|
|
44
|
+
total,
|
|
45
|
+
start,
|
|
46
|
+
end,
|
|
47
|
+
pageSize,
|
|
48
|
+
onPage,
|
|
49
|
+
onPageSize,
|
|
50
|
+
}: {
|
|
51
|
+
page: number;
|
|
52
|
+
pageCount: number;
|
|
53
|
+
total: number;
|
|
54
|
+
start: number;
|
|
55
|
+
end: number;
|
|
56
|
+
pageSize: number;
|
|
57
|
+
onPage: (p: number) => void;
|
|
58
|
+
onPageSize: (n: number) => void;
|
|
59
|
+
}) {
|
|
60
|
+
// Nothing to page when the whole set fits in the smallest page size.
|
|
61
|
+
if (total <= PAGE_SIZES[0]) return null;
|
|
62
|
+
return (
|
|
63
|
+
<div className="mt-3 flex flex-wrap items-center justify-between gap-3 text-xs text-muted-fg">
|
|
64
|
+
<div className="flex items-center gap-3">
|
|
65
|
+
<span className="tabular-nums">{t("common.pager_range", { from: start + 1, to: end, total })}</span>
|
|
66
|
+
<span className="flex items-center gap-1.5">
|
|
67
|
+
<span>{t("common.pager_per_page")}</span>
|
|
68
|
+
<div className="w-[4.5rem]">
|
|
69
|
+
<UiSelect
|
|
70
|
+
value={String(pageSize)}
|
|
71
|
+
onChange={(v) => onPageSize(Number(v))}
|
|
72
|
+
options={PAGE_SIZES.map((n) => ({ value: String(n), label: String(n) }))}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-1">
|
|
78
|
+
<Button size="sm" variant="ghost" disabled={page <= 1} onClick={() => onPage(page - 1)} aria-label={t("common.pager_prev")}>
|
|
79
|
+
<ChevronLeft size={14} />
|
|
80
|
+
</Button>
|
|
81
|
+
<span className="px-1 tabular-nums">{t("common.pager_page", { page, total: pageCount })}</span>
|
|
82
|
+
<Button size="sm" variant="ghost" disabled={page >= pageCount} onClick={() => onPage(page + 1)} aria-label={t("common.pager_next")}>
|
|
83
|
+
<ChevronRight size={14} />
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -32,6 +32,11 @@ export const en = {
|
|
|
32
32
|
hide: "Hide",
|
|
33
33
|
copy: "Copy",
|
|
34
34
|
run: "Run",
|
|
35
|
+
pager_prev: "Previous",
|
|
36
|
+
pager_next: "Next",
|
|
37
|
+
pager_page: "Page {page} of {total}",
|
|
38
|
+
pager_range: "{from}–{to} of {total}",
|
|
39
|
+
pager_per_page:"Per page",
|
|
35
40
|
},
|
|
36
41
|
daemon: {
|
|
37
42
|
connecting: "Connecting to the daemon…",
|
|
@@ -33,6 +33,11 @@ export const es = {
|
|
|
33
33
|
hide: "Ocultar",
|
|
34
34
|
copy: "Copiar",
|
|
35
35
|
run: "Ejecutar",
|
|
36
|
+
pager_prev: "Anterior",
|
|
37
|
+
pager_next: "Siguiente",
|
|
38
|
+
pager_page: "Página {page} de {total}",
|
|
39
|
+
pager_range: "{from}–{to} de {total}",
|
|
40
|
+
pager_per_page:"Por página",
|
|
36
41
|
},
|
|
37
42
|
daemon: {
|
|
38
43
|
connecting: "Conectando con el daemon…",
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|