fleetlens 0.13.0 → 0.13.2
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/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/build-manifest.json +8 -8
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/server/app/_global-error/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +7 -7
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/changelog/page.js +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/day/page.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/digest/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/insights/page.js +3 -2
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/page.js +1 -1
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/runs/page.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/settings/page.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/team/page.js +1 -1
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page/build-manifest.json +5 -5
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0gril0u._.js +9 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0n79hpm._.js +9 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~utv~v._.js +9 -0
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0-1_njm._.js → [root-of-the-server]__0k1x-c9._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0lhwp-k._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +3 -3
- package/app/apps/web/.next/server/chunks/ssr/_0kajk31._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/_0s3n5mz._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{_0jw2sm7._.js → _12mbgs5._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_insights_page_tsx_071kwyw._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_sessions_sessions-grid_tsx_0bri5x.._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0l6hsch._.js +6 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0cepfi8._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +8 -8
- package/app/apps/web/.next/server/middleware-manifest.json +5 -5
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/02xxpexzk3rf-.js +1 -0
- package/app/apps/web/.next/static/chunks/{07lbm-sdh9c2j.js → 0389wb_f0j78j.js} +1 -1
- package/app/apps/web/.next/static/chunks/03gy1evbn94qu.js +6 -0
- package/app/apps/web/.next/static/chunks/03rd.3cc_t~yf.js +4 -0
- package/app/apps/web/.next/static/chunks/{0l4e3as18eexy.js → 0g223u9amib0q.js} +1 -1
- package/app/apps/web/.next/static/chunks/{0h3.-_s-k0mj3.js → 0i.hc~2bj0_3d.js} +1 -1
- package/app/apps/web/.next/static/chunks/0m7dd-2zd9vl2.js +4 -0
- package/app/apps/web/.next/static/chunks/0nqklmbs5s3ax.js +6 -0
- package/app/apps/web/.next/static/chunks/{073081qv4uyn8.js → 0p--hzrway8~3.js} +1 -1
- package/app/apps/web/.next/static/chunks/{0gblp_v.kiu-..js → 0qv-gw888_5nu.js} +1 -1
- package/app/apps/web/.next/static/chunks/{14bjditmmrn7l.js → 0tirv_vo6q8uc.js} +1 -1
- package/app/apps/web/.next/static/chunks/0uyctl-m8iydb.js +1 -0
- package/app/apps/web/.next/static/chunks/0~qdw1b2uz._5.js +10 -0
- package/app/apps/web/.next/static/chunks/{0n0n.0279ot6c.js → 14vx702c32kn6.js} +1 -1
- package/app/apps/web/.next/static/chunks/{turbopack-0lb9w090w5ezx.js → turbopack-0mxu3~9vk.brh.js} +1 -1
- package/app/apps/web/app/api/digest/_shared/digest-route-helpers.ts +178 -0
- package/app/apps/web/app/api/digest/day/[date]/route.ts +34 -128
- package/app/apps/web/app/api/digest/month/[yearMonth]/route.ts +36 -121
- package/app/apps/web/app/api/digest/week/[startDate]/route.ts +36 -123
- package/app/apps/web/app/insights/page.tsx +20 -5
- package/app/apps/web/app/parallelism/burst-detail-modal.tsx +637 -0
- package/app/apps/web/app/parallelism/concurrency-info-modal.tsx +228 -0
- package/app/apps/web/app/parallelism/gantt-chart-utils.ts +49 -0
- package/app/apps/web/app/parallelism/gantt-chart.tsx +24 -903
- package/app/apps/web/app/sessions/[id]/session-view/colors.ts +31 -0
- package/app/apps/web/app/sessions/[id]/session-view/debug-list.tsx +78 -0
- package/app/apps/web/app/sessions/[id]/session-view/drawer.tsx +414 -0
- package/app/apps/web/app/sessions/[id]/session-view/entry-day-strip.tsx +124 -0
- package/app/apps/web/app/sessions/[id]/session-view/header-stats.tsx +217 -0
- package/app/apps/web/app/sessions/[id]/session-view/helpers.ts +135 -0
- package/app/apps/web/app/sessions/[id]/session-view/minimap.tsx +1587 -0
- package/app/apps/web/app/sessions/[id]/session-view/subagent-drawer.tsx +339 -0
- package/app/apps/web/app/sessions/[id]/session-view/token-stats.tsx +111 -0
- package/app/apps/web/app/sessions/[id]/session-view/tooltip.tsx +63 -0
- package/app/apps/web/app/sessions/[id]/session-view/types.ts +46 -0
- package/app/apps/web/app/sessions/[id]/session-view/workflows-panel.tsx +944 -0
- package/app/apps/web/app/sessions/[id]/session-view/workflows-shared.tsx +104 -0
- package/app/apps/web/app/sessions/[id]/session-view.tsx +1513 -5018
- package/app/apps/web/app/sessions/[id]/team-tab/minimap-hover-card.tsx +201 -0
- package/app/apps/web/app/sessions/[id]/team-tab/minimap-idle-band.tsx +53 -0
- package/app/apps/web/app/sessions/[id]/team-tab/minimap-shared.ts +20 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +10 -265
- package/app/apps/web/app/sessions/sessions-grid.tsx +10 -10
- package/app/apps/web/components/digest-actions.tsx +103 -0
- package/app/apps/web/components/job-queue-widget.tsx +1 -1
- package/app/apps/web/components/month-digest-view.tsx +19 -64
- package/app/apps/web/components/searchable-select.tsx +215 -0
- package/app/apps/web/components/tail-mode.tsx +46 -9
- package/app/apps/web/components/week-digest-view.tsx +16 -71
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/lib/entries-index.ts +7 -8
- package/app/apps/web/lib/entries.ts +0 -9
- package/app/apps/web/lib/format.test.ts +221 -0
- package/app/apps/web/lib/format.ts +17 -0
- package/app/apps/web/lib/utilization-tone.test.ts +56 -0
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +35 -43
- package/dist/index.js +37 -49
- package/package.json +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__04j3tng._.js +0 -9
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0cnh3k4._.js +0 -9
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__132eosx._.js +0 -9
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__136-0me._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/_0w.cv64._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_01d-iwx._.js +0 -6
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +0 -3
- package/app/apps/web/.next/static/chunks/05k84gif3ucxm.js +0 -6
- package/app/apps/web/.next/static/chunks/0nnjldasalwdu.js +0 -4
- package/app/apps/web/.next/static/chunks/0okg13phslr1..js +0 -4
- package/app/apps/web/.next/static/chunks/0pzt9vemtzct8.js +0 -10
- package/app/apps/web/.next/static/chunks/0qmh5da6gfm56.js +0 -1
- package/app/apps/web/.next/static/chunks/0rnay-a.-jr8-.js +0 -1
- package/app/apps/web/.next/static/chunks/0snv3-2ux2xvv.js +0 -6
- /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → bZs5UyzsYBqC6MFY5n-M_}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → bZs5UyzsYBqC6MFY5n-M_}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{rbp9x3iPbzrz-wxS-DAU5 → bZs5UyzsYBqC6MFY5n-M_}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { ExportPdfButton } from "./export-pdf-button";
|
|
4
|
+
|
|
5
|
+
type Period = "week" | "month";
|
|
6
|
+
|
|
7
|
+
export function DigestActions({
|
|
8
|
+
digestKey,
|
|
9
|
+
period,
|
|
10
|
+
hasDigest,
|
|
11
|
+
narrativeFresh,
|
|
12
|
+
isStreaming,
|
|
13
|
+
aiEnabled,
|
|
14
|
+
progress,
|
|
15
|
+
onGenerate,
|
|
16
|
+
onReroll,
|
|
17
|
+
rerollTitle,
|
|
18
|
+
}: {
|
|
19
|
+
digestKey: string;
|
|
20
|
+
period: Period;
|
|
21
|
+
hasDigest: boolean;
|
|
22
|
+
narrativeFresh: boolean;
|
|
23
|
+
isStreaming: boolean;
|
|
24
|
+
aiEnabled: boolean;
|
|
25
|
+
progress: string;
|
|
26
|
+
onGenerate: () => void;
|
|
27
|
+
onReroll: () => void;
|
|
28
|
+
rerollTitle?: string;
|
|
29
|
+
}): ReactNode {
|
|
30
|
+
const defaultRerollTitle = `Re-roll the narrative for this ${period}`;
|
|
31
|
+
const pdfAction = hasDigest ? <ExportPdfButton digestKey={`${period}-${digestKey}`} /> : null;
|
|
32
|
+
|
|
33
|
+
let actions: ReactNode = null;
|
|
34
|
+
if (!narrativeFresh && aiEnabled) {
|
|
35
|
+
actions = (
|
|
36
|
+
<button
|
|
37
|
+
onClick={onGenerate}
|
|
38
|
+
disabled={isStreaming}
|
|
39
|
+
style={btnPrimary(isStreaming)}
|
|
40
|
+
>
|
|
41
|
+
{isStreaming ? "Generating..." : hasDigest ? "Generate" : "Generate digest"}
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
} else if (aiEnabled) {
|
|
45
|
+
actions = (
|
|
46
|
+
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)", display: "inline-flex", gap: 6, alignItems: "center" }}>
|
|
47
|
+
✓ Up to date
|
|
48
|
+
<button
|
|
49
|
+
onClick={onReroll}
|
|
50
|
+
disabled={isStreaming}
|
|
51
|
+
title={rerollTitle ?? defaultRerollTitle}
|
|
52
|
+
style={btnSecondary(isStreaming)}
|
|
53
|
+
>
|
|
54
|
+
{isStreaming ? "..." : "Re-roll"}
|
|
55
|
+
</button>
|
|
56
|
+
</span>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (progress) {
|
|
60
|
+
actions = (
|
|
61
|
+
<>
|
|
62
|
+
{actions}
|
|
63
|
+
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)" }}>{progress}</span>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (pdfAction) {
|
|
68
|
+
actions = (
|
|
69
|
+
<>
|
|
70
|
+
{actions}
|
|
71
|
+
{pdfAction}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return actions;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function btnPrimary(disabled: boolean): React.CSSProperties {
|
|
79
|
+
return {
|
|
80
|
+
padding: "4px 10px",
|
|
81
|
+
background: "var(--af-accent)",
|
|
82
|
+
color: "white",
|
|
83
|
+
border: "none",
|
|
84
|
+
borderRadius: 5,
|
|
85
|
+
cursor: disabled ? "default" : "pointer",
|
|
86
|
+
fontSize: 11,
|
|
87
|
+
fontWeight: 600,
|
|
88
|
+
opacity: disabled ? 0.6 : 1,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function btnSecondary(disabled: boolean): React.CSSProperties {
|
|
93
|
+
return {
|
|
94
|
+
padding: "2px 7px",
|
|
95
|
+
background: "transparent",
|
|
96
|
+
border: "1px solid var(--af-border-subtle)",
|
|
97
|
+
borderRadius: 4,
|
|
98
|
+
fontSize: 10,
|
|
99
|
+
color: "var(--af-text-secondary)",
|
|
100
|
+
cursor: disabled ? "default" : "pointer",
|
|
101
|
+
opacity: disabled ? 0.6 : 1,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -199,7 +199,7 @@ function JobRow({ job }: { job: Job }) {
|
|
|
199
199
|
job.status === "error" ? "#f56565" :
|
|
200
200
|
"#48bb78";
|
|
201
201
|
const isLink = job.resultUrl && (job.status === "done" || job.status === "running");
|
|
202
|
-
const Container:
|
|
202
|
+
const Container: React.ElementType = isLink ? Link : "div";
|
|
203
203
|
const containerProps = isLink ? { href: job.resultUrl! } : {};
|
|
204
204
|
|
|
205
205
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState, useCallback
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
3
|
import { MonthDigest as MonthDigestRender } from "./month-digest";
|
|
4
|
-
import {
|
|
4
|
+
import { DigestActions } from "./digest-actions";
|
|
5
5
|
import type { MonthDigest as MonthDigestType } from "@claude-lens/entries";
|
|
6
6
|
|
|
7
7
|
type Status = "idle" | "streaming" | "done" | "error";
|
|
@@ -67,75 +67,30 @@ export function MonthDigestView({
|
|
|
67
67
|
|
|
68
68
|
const isStreaming = status === "streaming";
|
|
69
69
|
const narrativeFresh = !!digest?.headline;
|
|
70
|
-
const pdfAction = digest ? <ExportPdfButton digestKey={`month-${yearMonth}`} /> : null;
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
fontSize: 11,
|
|
86
|
-
fontWeight: 600,
|
|
87
|
-
opacity: isStreaming ? 0.6 : 1,
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
90
|
-
{isStreaming ? "Generating..." : digest ? "Generate" : "Generate digest"}
|
|
91
|
-
</button>
|
|
92
|
-
);
|
|
93
|
-
} else if (aiEnabled) {
|
|
94
|
-
actions = (
|
|
95
|
-
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)", display: "inline-flex", gap: 6, alignItems: "center" }}>
|
|
96
|
-
✓ Up to date
|
|
97
|
-
<button
|
|
98
|
-
onClick={() => generate(true)}
|
|
99
|
-
disabled={isStreaming}
|
|
100
|
-
style={{
|
|
101
|
-
padding: "2px 7px",
|
|
102
|
-
background: "transparent",
|
|
103
|
-
border: "1px solid var(--af-border-subtle)",
|
|
104
|
-
borderRadius: 4,
|
|
105
|
-
fontSize: 10,
|
|
106
|
-
color: "var(--af-text-secondary)",
|
|
107
|
-
cursor: isStreaming ? "default" : "pointer",
|
|
108
|
-
opacity: isStreaming ? 0.6 : 1,
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
{isStreaming ? "..." : "Re-roll"}
|
|
112
|
-
</button>
|
|
113
|
-
</span>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (progress) {
|
|
117
|
-
actions = (
|
|
118
|
-
<>
|
|
119
|
-
{actions}
|
|
120
|
-
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)" }}>{progress}</span>
|
|
121
|
-
</>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
if (pdfAction) {
|
|
125
|
-
actions = (
|
|
126
|
-
<>
|
|
127
|
-
{actions}
|
|
128
|
-
{pdfAction}
|
|
129
|
-
</>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
71
|
+
const actions = (
|
|
72
|
+
<DigestActions
|
|
73
|
+
digestKey={yearMonth}
|
|
74
|
+
period="month"
|
|
75
|
+
hasDigest={!!digest}
|
|
76
|
+
narrativeFresh={narrativeFresh}
|
|
77
|
+
isStreaming={isStreaming}
|
|
78
|
+
aiEnabled={aiEnabled}
|
|
79
|
+
progress={progress}
|
|
80
|
+
onGenerate={() => generate(false)}
|
|
81
|
+
onReroll={() => generate(true)}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
132
84
|
|
|
133
85
|
if (digest) {
|
|
134
86
|
return <MonthDigestRender digest={digest} aiEnabled={aiEnabled} actions={actions} />;
|
|
135
87
|
}
|
|
88
|
+
// Hide the action bar when DigestActions would render nothing (no AI, no progress,
|
|
89
|
+
// no digest → no PDF). Otherwise the empty wrapper leaves visible padding.
|
|
90
|
+
const hasActionContent = aiEnabled || !!progress;
|
|
136
91
|
return (
|
|
137
92
|
<>
|
|
138
|
-
{
|
|
93
|
+
{hasActionContent && (
|
|
139
94
|
<div style={{ display: "flex", gap: 10, padding: "14px 40px 0", alignItems: "center", flexWrap: "wrap" }}>
|
|
140
95
|
{actions}
|
|
141
96
|
</div>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { ChevronDown, Search } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
export type SelectOption = { value: string; label: string };
|
|
7
|
+
|
|
8
|
+
/** A native <select> can't be filtered, which is painful once there are dozens
|
|
9
|
+
* of options (e.g. the project list across many folders). This is a drop-in
|
|
10
|
+
* combobox: click to open, type to filter by label, arrow/Enter/Escape to
|
|
11
|
+
* navigate. Selection is still a single string value via onChange. */
|
|
12
|
+
export function SearchableSelect({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
options,
|
|
16
|
+
placeholder = "Select…",
|
|
17
|
+
searchPlaceholder = "Type to filter…",
|
|
18
|
+
maxWidth = 260,
|
|
19
|
+
title,
|
|
20
|
+
}: {
|
|
21
|
+
value: string;
|
|
22
|
+
onChange: (v: string) => void;
|
|
23
|
+
options: SelectOption[];
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
searchPlaceholder?: string;
|
|
26
|
+
maxWidth?: number;
|
|
27
|
+
title?: string;
|
|
28
|
+
}) {
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const [q, setQ] = useState("");
|
|
31
|
+
const [active, setActive] = useState(0);
|
|
32
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
33
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
34
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const baseId = useId();
|
|
36
|
+
const listboxId = `${baseId}-listbox`;
|
|
37
|
+
const optionId = (i: number) => `${baseId}-opt-${i}`;
|
|
38
|
+
|
|
39
|
+
const selected = options.find((o) => o.value === value);
|
|
40
|
+
const filtered = useMemo(() => {
|
|
41
|
+
const s = q.trim().toLowerCase();
|
|
42
|
+
if (!s) return options;
|
|
43
|
+
return options.filter((o) => o.label.toLowerCase().includes(s));
|
|
44
|
+
}, [q, options]);
|
|
45
|
+
|
|
46
|
+
// On open only: clear the filter and highlight the current value (a native
|
|
47
|
+
// select opens on its selection), then focus the search box. Deps are [open]
|
|
48
|
+
// alone on purpose — `options` is a fresh array each parent render, so
|
|
49
|
+
// including it would reset the filter mid-typing.
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!open) return;
|
|
52
|
+
setQ("");
|
|
53
|
+
setActive(Math.max(0, options.findIndex((o) => o.value === value)));
|
|
54
|
+
const id = requestAnimationFrame(() => inputRef.current?.focus());
|
|
55
|
+
return () => cancelAnimationFrame(id);
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, [open]);
|
|
58
|
+
|
|
59
|
+
// Keep the active option scrolled into view as the user arrows through.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const el = listRef.current?.children[active] as HTMLElement | undefined;
|
|
62
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
63
|
+
}, [active]);
|
|
64
|
+
|
|
65
|
+
// Close and return focus to the trigger (a native select refocuses itself).
|
|
66
|
+
const close = () => {
|
|
67
|
+
setOpen(false);
|
|
68
|
+
buttonRef.current?.focus();
|
|
69
|
+
};
|
|
70
|
+
const choose = (v: string) => {
|
|
71
|
+
onChange(v);
|
|
72
|
+
close();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const activeId = filtered[active] ? optionId(active) : undefined;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div style={{ position: "relative", maxWidth }}>
|
|
79
|
+
<button
|
|
80
|
+
ref={buttonRef}
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={() => setOpen((o) => !o)}
|
|
83
|
+
title={title}
|
|
84
|
+
aria-haspopup="listbox"
|
|
85
|
+
aria-expanded={open}
|
|
86
|
+
style={{
|
|
87
|
+
display: "flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
gap: 6,
|
|
90
|
+
width: "100%",
|
|
91
|
+
background: "var(--background)",
|
|
92
|
+
border: `1px solid ${open ? "var(--af-accent)" : "var(--af-border-subtle)"}`,
|
|
93
|
+
borderRadius: 8,
|
|
94
|
+
padding: "9px 12px",
|
|
95
|
+
color: "var(--af-text)",
|
|
96
|
+
fontSize: 13,
|
|
97
|
+
cursor: "pointer",
|
|
98
|
+
textAlign: "left",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<span
|
|
102
|
+
style={{
|
|
103
|
+
overflow: "hidden",
|
|
104
|
+
textOverflow: "ellipsis",
|
|
105
|
+
whiteSpace: "nowrap",
|
|
106
|
+
color: selected ? "var(--af-text)" : "var(--af-text-tertiary)",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{selected?.label ?? placeholder}
|
|
110
|
+
</span>
|
|
111
|
+
<ChevronDown size={13} style={{ marginLeft: "auto", flexShrink: 0, color: "var(--af-text-tertiary)" }} />
|
|
112
|
+
</button>
|
|
113
|
+
|
|
114
|
+
{open && (
|
|
115
|
+
<>
|
|
116
|
+
{/* Click-away backdrop. */}
|
|
117
|
+
<div onClick={() => setOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 40 }} />
|
|
118
|
+
<div
|
|
119
|
+
style={{
|
|
120
|
+
position: "absolute",
|
|
121
|
+
top: "calc(100% + 4px)",
|
|
122
|
+
left: 0,
|
|
123
|
+
minWidth: "100%",
|
|
124
|
+
width: "max-content",
|
|
125
|
+
maxWidth: 380,
|
|
126
|
+
zIndex: 41,
|
|
127
|
+
background: "var(--af-surface)",
|
|
128
|
+
border: "1px solid var(--af-border-subtle)",
|
|
129
|
+
borderRadius: 8,
|
|
130
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.28)",
|
|
131
|
+
overflow: "hidden",
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<div style={{ position: "relative", padding: 6, borderBottom: "1px solid var(--af-border-subtle)" }}>
|
|
135
|
+
<Search
|
|
136
|
+
size={12}
|
|
137
|
+
style={{ position: "absolute", left: 15, top: "50%", transform: "translateY(-50%)", color: "var(--af-text-tertiary)" }}
|
|
138
|
+
/>
|
|
139
|
+
<input
|
|
140
|
+
ref={inputRef}
|
|
141
|
+
type="text"
|
|
142
|
+
role="combobox"
|
|
143
|
+
aria-expanded={open}
|
|
144
|
+
aria-controls={listboxId}
|
|
145
|
+
aria-activedescendant={activeId}
|
|
146
|
+
aria-autocomplete="list"
|
|
147
|
+
value={q}
|
|
148
|
+
onChange={(e) => {
|
|
149
|
+
setQ(e.target.value);
|
|
150
|
+
setActive(0); // typing narrows the list; keep the highlight on a real row
|
|
151
|
+
}}
|
|
152
|
+
placeholder={searchPlaceholder}
|
|
153
|
+
onKeyDown={(e) => {
|
|
154
|
+
if (e.key === "ArrowDown") {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
setActive((a) => Math.min(a + 1, Math.max(0, filtered.length - 1)));
|
|
157
|
+
} else if (e.key === "ArrowUp") {
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
setActive((a) => Math.max(a - 1, 0));
|
|
160
|
+
} else if (e.key === "Enter") {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
const opt = filtered[active];
|
|
163
|
+
if (opt) choose(opt.value);
|
|
164
|
+
} else if (e.key === "Escape") {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
close();
|
|
167
|
+
} else if (e.key === "Tab") {
|
|
168
|
+
// Tab moves browser focus out naturally; close the dropdown
|
|
169
|
+
// so it doesn't linger visible until a click-away.
|
|
170
|
+
setOpen(false);
|
|
171
|
+
}
|
|
172
|
+
}}
|
|
173
|
+
style={{ width: "100%", paddingLeft: 30, fontSize: 12 }}
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
<div ref={listRef} role="listbox" id={listboxId} style={{ maxHeight: 300, overflowY: "auto", padding: 4 }}>
|
|
177
|
+
{filtered.length === 0 ? (
|
|
178
|
+
<div style={{ padding: "8px 10px", fontSize: 12, color: "var(--af-text-tertiary)" }}>No matches</div>
|
|
179
|
+
) : (
|
|
180
|
+
filtered.map((o, i) => (
|
|
181
|
+
<button
|
|
182
|
+
key={o.value}
|
|
183
|
+
id={optionId(i)}
|
|
184
|
+
type="button"
|
|
185
|
+
role="option"
|
|
186
|
+
aria-selected={o.value === value}
|
|
187
|
+
onClick={() => choose(o.value)}
|
|
188
|
+
onMouseEnter={() => setActive(i)}
|
|
189
|
+
style={{
|
|
190
|
+
display: "block",
|
|
191
|
+
width: "100%",
|
|
192
|
+
textAlign: "left",
|
|
193
|
+
padding: "6px 10px",
|
|
194
|
+
fontSize: 12,
|
|
195
|
+
border: "none",
|
|
196
|
+
borderRadius: 4,
|
|
197
|
+
cursor: "pointer",
|
|
198
|
+
background: i === active ? "var(--af-surface-hover)" : "transparent",
|
|
199
|
+
color: o.value === value ? "var(--af-accent)" : "var(--af-text)",
|
|
200
|
+
whiteSpace: "nowrap",
|
|
201
|
+
overflow: "hidden",
|
|
202
|
+
textOverflow: "ellipsis",
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
{o.label}
|
|
206
|
+
</button>
|
|
207
|
+
))
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
@@ -11,10 +11,20 @@
|
|
|
11
11
|
* - Shows "Following live" indicator when tailing
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { useEffect, useRef, useState, useCallback } from "react";
|
|
14
|
+
import { useEffect, useRef, useState, useCallback, type MutableRefObject } from "react";
|
|
15
15
|
import { ArrowDown, Radio } from "lucide-react";
|
|
16
16
|
|
|
17
|
-
export function TailMode({
|
|
17
|
+
export function TailMode({
|
|
18
|
+
isLive,
|
|
19
|
+
navLockRef,
|
|
20
|
+
}: {
|
|
21
|
+
isLive: boolean;
|
|
22
|
+
/** SessionView stamps Date.now() here on every explicit day-jump. While the
|
|
23
|
+
* stamp is fresh the live-follow observer bails, so a streaming event can't
|
|
24
|
+
* yank the view back to the bottom mid-navigation. Read synchronously — a
|
|
25
|
+
* setState couldn't disconnect the observer before its callback fires. */
|
|
26
|
+
navLockRef?: MutableRefObject<number>;
|
|
27
|
+
}) {
|
|
18
28
|
const [tailing, setTailing] = useState(isLive);
|
|
19
29
|
const [atBottom, setAtBottom] = useState(false);
|
|
20
30
|
const mainRef = useRef<HTMLElement | null>(null);
|
|
@@ -24,14 +34,25 @@ export function TailMode({ isLive }: { isLive: boolean }) {
|
|
|
24
34
|
useEffect(() => {
|
|
25
35
|
mainRef.current = document.querySelector("main");
|
|
26
36
|
if (isLive && mainRef.current) {
|
|
27
|
-
// Small delay so the initial render settles before scrolling.
|
|
37
|
+
// Small delay so the initial render settles before scrolling. Use an
|
|
38
|
+
// INSTANT scroll, not smooth: a smooth animation emits a stream of scroll
|
|
39
|
+
// events, and check() reads the second one (still mid-animation, not yet
|
|
40
|
+
// at the bottom) as a user scroll-up and kills tailing — so on a heavy
|
|
41
|
+
// session the view never reaches the live tail. Instant lands in one
|
|
42
|
+
// event, which check() correctly sees as "at bottom".
|
|
28
43
|
const t = setTimeout(() => {
|
|
44
|
+
// An explicit day-jump (gotoDay, e.g. opening a live session via
|
|
45
|
+
// ?day=<earlier day>) stamps navLockRef and scrolls to that past day —
|
|
46
|
+
// don't yank the view back to the live tail on top of it.
|
|
47
|
+
if (navLockRef && Date.now() - navLockRef.current < 1500) return;
|
|
29
48
|
userScrolledRef.current = false;
|
|
30
|
-
mainRef.current?.scrollTo({ top: mainRef.current.scrollHeight, behavior: "
|
|
49
|
+
mainRef.current?.scrollTo({ top: mainRef.current.scrollHeight, behavior: "auto" });
|
|
31
50
|
}, 300);
|
|
32
51
|
return () => clearTimeout(t);
|
|
33
52
|
}
|
|
34
|
-
|
|
53
|
+
// navLockRef is a stable ref; listed to satisfy exhaustive-deps without
|
|
54
|
+
// causing re-runs (mirrors the observer effect below).
|
|
55
|
+
}, [isLive, navLockRef]);
|
|
35
56
|
|
|
36
57
|
// Track whether we're at the bottom.
|
|
37
58
|
useEffect(() => {
|
|
@@ -67,18 +88,34 @@ export function TailMode({ isLive }: { isLive: boolean }) {
|
|
|
67
88
|
// Use a MutationObserver to detect when new content is added
|
|
68
89
|
// (the RSC re-render appends new transcript rows to the DOM).
|
|
69
90
|
const observer = new MutationObserver(() => {
|
|
91
|
+
// Bail while a day-jump is in flight: SessionView just scrolled the view
|
|
92
|
+
// to an earlier day, and a streaming live event must NOT yank it back to
|
|
93
|
+
// the bottom (that fight was the day-nav "flicker" on live sessions). A
|
|
94
|
+
// moment later check() sees the scroll is away from the bottom and turns
|
|
95
|
+
// tailing off for good. NOTE: do not gate on distance-from-bottom instead
|
|
96
|
+
// — on a heavy session the content streams in while we're still far from
|
|
97
|
+
// the bottom, and a distance gate would abandon the initial catch-up.
|
|
98
|
+
if (navLockRef && Date.now() - navLockRef.current < 1500) return;
|
|
70
99
|
userScrolledRef.current = false; // suppress the scroll-up detection
|
|
71
|
-
|
|
100
|
+
// Instant, not smooth: a smooth animation emits scroll events across many
|
|
101
|
+
// frames and check() reads a mid-animation one (still away from the bottom)
|
|
102
|
+
// as a user scroll-up, killing tailing after the first sizeable streamed
|
|
103
|
+
// append. Same reason as the immediate scroll below and the mount scroll.
|
|
104
|
+
main.scrollTo({ top: main.scrollHeight, behavior: "auto" });
|
|
72
105
|
});
|
|
73
106
|
|
|
74
107
|
observer.observe(main, { childList: true, subtree: true });
|
|
75
108
|
|
|
76
|
-
// Also scroll immediately when tail mode is first enabled.
|
|
109
|
+
// Also scroll immediately when tail mode is first enabled. Instant (not
|
|
110
|
+
// smooth) for the same reason as the mount scroll above — a smooth
|
|
111
|
+
// animation's scroll events trip check() into disabling tailing mid-flight.
|
|
77
112
|
userScrolledRef.current = false;
|
|
78
|
-
main.scrollTo({ top: main.scrollHeight, behavior: "
|
|
113
|
+
main.scrollTo({ top: main.scrollHeight, behavior: "auto" });
|
|
79
114
|
|
|
80
115
|
return () => observer.disconnect();
|
|
81
|
-
|
|
116
|
+
// navLockRef is a stable ref; listed to satisfy exhaustive-deps without
|
|
117
|
+
// causing re-runs.
|
|
118
|
+
}, [tailing, navLockRef]);
|
|
82
119
|
|
|
83
120
|
const jumpToLatest = useCallback(() => {
|
|
84
121
|
const main = mainRef.current;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState, useCallback, useEffect, useRef
|
|
2
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
3
3
|
import { WeekDigest as WeekDigestRender } from "./week-digest";
|
|
4
|
-
import {
|
|
4
|
+
import { DigestActions } from "./digest-actions";
|
|
5
5
|
import type { WeekDigest as WeekDigestType } from "@claude-lens/entries";
|
|
6
6
|
|
|
7
7
|
type Status = "idle" | "streaming" | "done" | "error";
|
|
@@ -77,50 +77,21 @@ export function WeekDigestView({
|
|
|
77
77
|
|
|
78
78
|
const isStreaming = status === "streaming";
|
|
79
79
|
const narrativeFresh = !!digest?.headline;
|
|
80
|
-
const pdfAction = digest ? <ExportPdfButton digestKey={`week-${monday}`} /> : null;
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
✓ Up to date
|
|
97
|
-
<button
|
|
98
|
-
onClick={() => generate(true)}
|
|
99
|
-
disabled={isStreaming}
|
|
100
|
-
title="Re-roll the narrative for this week"
|
|
101
|
-
style={btnSecondary(isStreaming)}
|
|
102
|
-
>
|
|
103
|
-
{isStreaming ? "..." : "Re-roll"}
|
|
104
|
-
</button>
|
|
105
|
-
</span>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
if (progress) {
|
|
109
|
-
actions = (
|
|
110
|
-
<>
|
|
111
|
-
{actions}
|
|
112
|
-
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)" }}>{progress}</span>
|
|
113
|
-
</>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (pdfAction) {
|
|
117
|
-
actions = (
|
|
118
|
-
<>
|
|
119
|
-
{actions}
|
|
120
|
-
{pdfAction}
|
|
121
|
-
</>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
81
|
+
const actions = (
|
|
82
|
+
<DigestActions
|
|
83
|
+
digestKey={monday}
|
|
84
|
+
period="week"
|
|
85
|
+
hasDigest={!!digest}
|
|
86
|
+
narrativeFresh={narrativeFresh}
|
|
87
|
+
isStreaming={isStreaming}
|
|
88
|
+
aiEnabled={aiEnabled}
|
|
89
|
+
progress={progress}
|
|
90
|
+
onGenerate={() => generate(false)}
|
|
91
|
+
onReroll={() => generate(true)}
|
|
92
|
+
rerollTitle="Re-roll the narrative for this week"
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
124
95
|
|
|
125
96
|
if (digest) {
|
|
126
97
|
return <WeekDigestRender digest={digest} aiEnabled={aiEnabled} actions={actions} priorDigest={prior ?? null} />;
|
|
@@ -208,29 +179,3 @@ function EmptyWeekState({ monday, isStreaming, aiEnabled, progress, onGenerate }
|
|
|
208
179
|
);
|
|
209
180
|
}
|
|
210
181
|
|
|
211
|
-
function btnPrimary(disabled: boolean): React.CSSProperties {
|
|
212
|
-
return {
|
|
213
|
-
padding: "4px 10px",
|
|
214
|
-
background: "var(--af-accent)",
|
|
215
|
-
color: "white",
|
|
216
|
-
border: "none",
|
|
217
|
-
borderRadius: 5,
|
|
218
|
-
cursor: disabled ? "default" : "pointer",
|
|
219
|
-
fontSize: 11,
|
|
220
|
-
fontWeight: 600,
|
|
221
|
-
opacity: disabled ? 0.6 : 1,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function btnSecondary(disabled: boolean): React.CSSProperties {
|
|
226
|
-
return {
|
|
227
|
-
padding: "2px 7px",
|
|
228
|
-
background: "transparent",
|
|
229
|
-
border: "1px solid var(--af-border-subtle)",
|
|
230
|
-
borderRadius: 4,
|
|
231
|
-
fontSize: 10,
|
|
232
|
-
color: "var(--af-text-secondary)",
|
|
233
|
-
cursor: disabled ? "default" : "pointer",
|
|
234
|
-
opacity: disabled ? 0.6 : 1,
|
|
235
|
-
};
|
|
236
|
-
}
|