claudeye 0.2.2 → 0.3.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.
Potentially problematic release.
This version of claudeye might be problematic. Click here for more details.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/routes-manifest.json +9 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +6 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/icon.png/route.js +2 -1
- package/.next/standalone/.next/server/app/icon.png/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +35 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f408c708._.js +21 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fde83e67._.js +3 -0
- package/.next/standalone/.next/server/chunks/ce889_server_app_api_download_[project]_[session]_route_actions_bbdd823f.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_64175717.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__070e2009._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a745465._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__bc37261c._.js → [root-of-the-server]__14f58da3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__164d9311._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2822fd21._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__31b4c2fd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__45656df2._.js → [root-of-the-server]__4e339665._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__55018089._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__6313e929._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__7e21395a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ee388ee0._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0b4924bd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_9e089768._.js → _1404b353._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_3d21dde5._.js +8 -0
- package/.next/standalone/.next/server/chunks/ssr/_fd9b1ff7._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_7769b563._.js → node_modules_next_dist_esm_eedfc1fd._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +35 -5
- package/.next/standalone/.next/static/chunks/0e266948a26a3cdf.js +1 -0
- package/.next/standalone/.next/static/chunks/2774382cf796393c.js +4 -0
- package/.next/standalone/.next/static/chunks/6189ca16caad4352.js +3 -0
- package/.next/standalone/.next/static/chunks/8111dbe882e31821.js +1 -0
- package/.next/standalone/.next/static/chunks/{5a424275276f2bb9.js → bdeaeb8c9876394b.js} +1 -1
- package/.next/standalone/.next/static/chunks/cdbb6932218650fd.js +1 -0
- package/.next/standalone/.next/static/chunks/ea03555bb726c073.css +1 -0
- package/.next/standalone/.next/static/chunks/f091501564eb2ea3.js +4 -0
- package/.next/standalone/.next/static/chunks/{turbopack-2315171089e56fad.js → turbopack-fc1f23734a087d36.js} +1 -1
- package/.next/standalone/README.md +528 -41
- package/.next/standalone/app/actions/run-enrichments.ts +26 -5
- package/.next/standalone/app/actions/run-evals.ts +26 -5
- package/.next/standalone/app/actions/run-subagent-enrichments.ts +89 -0
- package/.next/standalone/app/actions/run-subagent-evals.ts +88 -0
- package/.next/standalone/app/api/download/[project]/[session]/route.ts +49 -0
- package/.next/standalone/app/components/copy-button.tsx +37 -0
- package/.next/standalone/app/components/enrichment-results-panel.tsx +33 -13
- package/.next/standalone/app/components/eval-results-panel.tsx +33 -13
- package/.next/standalone/app/components/log-viewer/entry-row.tsx +43 -14
- package/.next/standalone/app/components/log-viewer/queue-divider.tsx +50 -7
- package/.next/standalone/app/components/log-viewer/tool-input-output.tsx +13 -3
- package/.next/standalone/app/components/project-list.tsx +11 -11
- package/.next/standalone/app/components/raw-log-viewer.tsx +80 -11
- package/.next/standalone/app/components/refresh-button.tsx +79 -0
- package/.next/standalone/app/components/sessions-list.tsx +23 -14
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +23 -12
- package/.next/standalone/bin/claudeye.mjs +112 -25
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/dist/app.js +10 -4
- package/.next/standalone/dist/condition-registry.js +20 -0
- package/.next/standalone/dist/enrich-registry.js +26 -3
- package/.next/standalone/dist/enrich-runner.js +68 -13
- package/.next/standalone/dist/registry.js +26 -3
- package/.next/standalone/dist/runner.js +78 -20
- package/.next/standalone/dist/server-spawn.js +58 -34
- package/.next/standalone/lib/cache/hash.ts +67 -0
- package/.next/standalone/lib/cache/index.ts +9 -0
- package/.next/standalone/lib/cache/local-backend.ts +81 -0
- package/.next/standalone/lib/cache/manager.ts +127 -0
- package/.next/standalone/lib/cache/types.ts +19 -0
- package/.next/standalone/lib/evals/app.ts +30 -7
- package/.next/standalone/lib/evals/condition-registry.ts +26 -0
- package/.next/standalone/lib/evals/enrich-registry.ts +29 -3
- package/.next/standalone/lib/evals/enrich-runner.ts +68 -14
- package/.next/standalone/lib/evals/enrich-types.ts +6 -1
- package/.next/standalone/lib/evals/index.ts +3 -1
- package/.next/standalone/lib/evals/registry.ts +29 -4
- package/.next/standalone/lib/evals/runner.ts +77 -20
- package/.next/standalone/lib/evals/server-spawn.ts +67 -41
- package/.next/standalone/lib/evals/types.ts +16 -0
- package/.next/standalone/lib/log-format.ts +22 -1
- package/.next/standalone/package-lock.json +244 -308
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/dev.ts +3 -1
- package/.next/standalone/scripts/parse-script-args.ts +30 -2
- package/.next/standalone/scripts/start.ts +3 -1
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/README.md +528 -41
- package/bin/claudeye.mjs +112 -25
- package/dist/app.d.ts +17 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -4
- package/dist/app.js.map +1 -1
- package/dist/condition-registry.d.ts +9 -0
- package/dist/condition-registry.d.ts.map +1 -0
- package/dist/condition-registry.js +20 -0
- package/dist/condition-registry.js.map +1 -0
- package/dist/enrich-registry.d.ts +5 -1
- package/dist/enrich-registry.d.ts.map +1 -1
- package/dist/enrich-registry.js +26 -3
- package/dist/enrich-registry.js.map +1 -1
- package/dist/enrich-runner.d.ts +3 -3
- package/dist/enrich-runner.d.ts.map +1 -1
- package/dist/enrich-runner.js +68 -13
- package/dist/enrich-runner.js.map +1 -1
- package/dist/enrich-types.d.ts +6 -1
- package/dist/enrich-types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/registry.d.ts +5 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +26 -3
- package/dist/registry.js.map +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +78 -20
- package/dist/runner.js.map +1 -1
- package/dist/server-spawn.d.ts +2 -1
- package/dist/server-spawn.d.ts.map +1 -1
- package/dist/server-spawn.js +58 -34
- package/dist/server-spawn.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__24a1e50a._.js +0 -21
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__32f115c9._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__476a1712._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4ddcabf2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ad593585._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__afd8e13b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__dd7ee810._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ff3004de._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_53472598._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_863b6ca8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_f7347c74._.js +0 -5
- package/.next/standalone/.next/static/chunks/2243ff2814e7a781.js +0 -3
- package/.next/standalone/.next/static/chunks/50531467396cea91.css +0 -1
- package/.next/standalone/.next/static/chunks/8f288c01f8d7ef2d.js +0 -1
- package/.next/standalone/.next/static/chunks/abab1b00b2788443.js +0 -4
- package/.next/standalone/.next/static/chunks/d250d7f6f0a8c325.js +0 -1
- package/.next/standalone/.next/static/chunks/d7a572a8b7eb1ec8.js +0 -1
- package/.next/standalone/.next/static/chunks/fb1b0b9da3f03023.js +0 -4
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_ssgManifest.js +0 -0
|
@@ -7,9 +7,12 @@ import { ENTRY_BORDER_COLORS } from "./constants";
|
|
|
7
7
|
import { TypeBadge } from "./type-badge";
|
|
8
8
|
import { ToolInputOutput } from "./tool-input-output";
|
|
9
9
|
import { StatsBar } from "./stats-bar";
|
|
10
|
+
import EvalResultsPanel from "@/app/components/eval-results-panel";
|
|
11
|
+
import EnrichmentResultsPanel from "@/app/components/enrichment-results-panel";
|
|
10
12
|
import { QueueDivider } from "./queue-divider";
|
|
11
13
|
import { UserContent, AssistantContent, GenericContent } from "./content-block-view";
|
|
12
|
-
import { formatLocalTimestamp } from "@/lib/log-format";
|
|
14
|
+
import { formatLocalTimestamp, getEntryTextContent } from "@/lib/log-format";
|
|
15
|
+
import { CopyButton } from "@/app/components/copy-button";
|
|
13
16
|
|
|
14
17
|
// ── Subagent Tool Card ──
|
|
15
18
|
|
|
@@ -108,7 +111,25 @@ export function SubagentToolCard({ block, projectName, sessionId }: SubagentTool
|
|
|
108
111
|
<div className="text-xs text-muted-foreground py-2">No entries found in subagent log.</div>
|
|
109
112
|
)}
|
|
110
113
|
{entries && entries.length > 0 && (
|
|
111
|
-
|
|
114
|
+
<>
|
|
115
|
+
<StatsBar entries={entries} compact />
|
|
116
|
+
<EvalResultsPanel
|
|
117
|
+
projectName={projectName}
|
|
118
|
+
sessionId={sessionId}
|
|
119
|
+
agentId={block.subagentId}
|
|
120
|
+
subagentType={block.subagentType}
|
|
121
|
+
subagentDescription={block.subagentDescription}
|
|
122
|
+
compact
|
|
123
|
+
/>
|
|
124
|
+
<EnrichmentResultsPanel
|
|
125
|
+
projectName={projectName}
|
|
126
|
+
sessionId={sessionId}
|
|
127
|
+
agentId={block.subagentId}
|
|
128
|
+
subagentType={block.subagentType}
|
|
129
|
+
subagentDescription={block.subagentDescription}
|
|
130
|
+
compact
|
|
131
|
+
/>
|
|
132
|
+
</>
|
|
112
133
|
)}
|
|
113
134
|
{entries && entries.map((entry) =>
|
|
114
135
|
entry.type === "queue-operation" ? (
|
|
@@ -142,27 +163,35 @@ interface EntryRowProps {
|
|
|
142
163
|
sessionId: string;
|
|
143
164
|
}
|
|
144
165
|
|
|
166
|
+
function EntryContent({ entry, projectName, sessionId }: EntryRowProps): React.ReactNode {
|
|
167
|
+
switch (entry.type) {
|
|
168
|
+
case "user":
|
|
169
|
+
return <UserContent entry={entry} />;
|
|
170
|
+
case "assistant":
|
|
171
|
+
return <AssistantContent entry={entry} projectName={projectName} sessionId={sessionId} />;
|
|
172
|
+
case "file-history-snapshot":
|
|
173
|
+
case "progress":
|
|
174
|
+
case "system":
|
|
175
|
+
return <GenericContent entry={entry} />;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
145
179
|
export const EntryRow = React.memo(function EntryRow({ entry, projectName, sessionId }: EntryRowProps) {
|
|
146
180
|
return (
|
|
147
181
|
<div
|
|
148
182
|
className={`border-l-4 ${ENTRY_BORDER_COLORS[entry.type]} bg-card/50 rounded-r-lg mb-2 hover:bg-muted/30 transition-colors`}
|
|
149
183
|
>
|
|
150
|
-
{/* Header row */}
|
|
151
184
|
<div className="flex items-center gap-2 px-4 py-2 border-b border-border/30">
|
|
152
185
|
<TypeBadge type={entry.type} />
|
|
153
|
-
<
|
|
154
|
-
{
|
|
155
|
-
|
|
186
|
+
<div className="flex items-center gap-1 ml-auto">
|
|
187
|
+
<CopyButton text={getEntryTextContent(entry)} />
|
|
188
|
+
<span className="text-xs font-mono text-muted-foreground whitespace-nowrap">
|
|
189
|
+
{formatLocalTimestamp(entry.timestampMs)}
|
|
190
|
+
</span>
|
|
191
|
+
</div>
|
|
156
192
|
</div>
|
|
157
|
-
{/* Content area */}
|
|
158
193
|
<div className="px-4 py-3">
|
|
159
|
-
|
|
160
|
-
{entry.type === "assistant" && (
|
|
161
|
-
<AssistantContent entry={entry} projectName={projectName} sessionId={sessionId} />
|
|
162
|
-
)}
|
|
163
|
-
{(entry.type === "file-history-snapshot" ||
|
|
164
|
-
entry.type === "progress" ||
|
|
165
|
-
entry.type === "system") && <GenericContent entry={entry} />}
|
|
194
|
+
<EntryContent entry={entry} projectName={projectName} sessionId={sessionId} />
|
|
166
195
|
</div>
|
|
167
196
|
</div>
|
|
168
197
|
);
|
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Play } from "lucide-react";
|
|
2
|
+
import { Play, ChevronRight } from "lucide-react";
|
|
3
3
|
import type { QueueOperationEntry } from "@/lib/log-entries";
|
|
4
4
|
import { formatLocalTimestamp } from "@/lib/log-format";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
interface QueueDividerProps {
|
|
7
|
+
entry: QueueOperationEntry;
|
|
8
|
+
isCollapsed?: boolean;
|
|
9
|
+
entryCount?: number;
|
|
10
|
+
onToggle?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const QueueDivider = React.memo(function QueueDivider({
|
|
14
|
+
entry,
|
|
15
|
+
isCollapsed,
|
|
16
|
+
entryCount,
|
|
17
|
+
onToggle,
|
|
18
|
+
}: QueueDividerProps) {
|
|
19
|
+
const interactive = typeof onToggle === "function";
|
|
20
|
+
|
|
21
|
+
const pill = (
|
|
22
|
+
<div className={`flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-medium${interactive ? " group-hover:bg-primary/20 transition-colors" : ""}`}>
|
|
23
|
+
{interactive ? (
|
|
24
|
+
<ChevronRight
|
|
25
|
+
className={`w-3 h-3 transition-transform ${isCollapsed ? "" : "rotate-90"}`}
|
|
26
|
+
/>
|
|
27
|
+
) : (
|
|
28
|
+
<Play className="w-3 h-3" />
|
|
29
|
+
)}
|
|
30
|
+
<span>{entry.label}</span>
|
|
31
|
+
<span className="text-muted-foreground">{formatLocalTimestamp(entry.timestampMs)}</span>
|
|
32
|
+
{interactive && isCollapsed && typeof entryCount === "number" && (
|
|
33
|
+
<span className="ml-1 px-1.5 py-0.5 rounded bg-primary/20 text-[10px] leading-none">
|
|
34
|
+
{entryCount} {entryCount === 1 ? "entry" : "entries"}
|
|
35
|
+
</span>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (interactive) {
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
onClick={onToggle}
|
|
45
|
+
className="group flex items-center gap-3 py-3 px-4 w-full cursor-pointer"
|
|
46
|
+
>
|
|
47
|
+
<div className="flex-1 h-px bg-primary/30" />
|
|
48
|
+
{pill}
|
|
49
|
+
<div className="flex-1 h-px bg-primary/30" />
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
7
54
|
return (
|
|
8
55
|
<div className="flex items-center gap-3 py-3 px-4">
|
|
9
56
|
<div className="flex-1 h-px bg-primary/30" />
|
|
10
|
-
|
|
11
|
-
<Play className="w-3 h-3" />
|
|
12
|
-
<span>{entry.label}</span>
|
|
13
|
-
<span className="text-muted-foreground">{formatLocalTimestamp(entry.timestampMs)}</span>
|
|
14
|
-
</div>
|
|
57
|
+
{pill}
|
|
15
58
|
<div className="flex-1 h-px bg-primary/30" />
|
|
16
59
|
</div>
|
|
17
60
|
);
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import type { ToolUseBlock } from "@/lib/log-entries";
|
|
2
3
|
import { formatInput, formatLocalTimestamp } from "@/lib/log-format";
|
|
4
|
+
import { CopyButton } from "@/app/components/copy-button";
|
|
3
5
|
|
|
4
6
|
export function ToolInputOutput({ block }: { block: ToolUseBlock }) {
|
|
7
|
+
const inputText = useMemo(() => formatInput(block), [block]);
|
|
8
|
+
|
|
5
9
|
return (
|
|
6
10
|
<div className="space-y-2">
|
|
7
11
|
<details open>
|
|
8
12
|
<summary className="text-xs text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
|
|
9
|
-
|
|
13
|
+
<span className="inline-flex items-center gap-1">
|
|
14
|
+
Input
|
|
15
|
+
<CopyButton text={inputText} />
|
|
16
|
+
</span>
|
|
10
17
|
</summary>
|
|
11
18
|
<pre className="mt-1 p-2 bg-muted/50 rounded text-xs whitespace-pre-wrap break-words max-h-64 overflow-y-auto">
|
|
12
|
-
{
|
|
19
|
+
{inputText}
|
|
13
20
|
</pre>
|
|
14
21
|
</details>
|
|
15
22
|
{block.result ? (
|
|
@@ -25,7 +32,10 @@ export function ToolInputOutput({ block }: { block: ToolUseBlock }) {
|
|
|
25
32
|
{block.result.content && (
|
|
26
33
|
<details open>
|
|
27
34
|
<summary className="text-xs text-muted-foreground cursor-pointer hover:text-foreground transition-colors">
|
|
28
|
-
|
|
35
|
+
<span className="inline-flex items-center gap-1">
|
|
36
|
+
Output
|
|
37
|
+
<CopyButton text={block.result.content} />
|
|
38
|
+
</span>
|
|
29
39
|
</summary>
|
|
30
40
|
<pre className="mt-1 p-2 bg-muted/50 rounded text-xs whitespace-pre-wrap break-words max-h-48 overflow-y-auto">
|
|
31
41
|
{block.result.content}
|
|
@@ -20,6 +20,7 @@ import Link from "next/link";
|
|
|
20
20
|
import PaginationControls from "./pagination-controls";
|
|
21
21
|
import DatePickerInput from "./date-picker-input";
|
|
22
22
|
|
|
23
|
+
|
|
23
24
|
interface ProjectListProps {
|
|
24
25
|
folders: ProjectFolder[];
|
|
25
26
|
}
|
|
@@ -93,7 +94,7 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
93
94
|
{/* Filter Bar */}
|
|
94
95
|
<div className="bg-card border border-border rounded-lg p-4">
|
|
95
96
|
<div className="flex flex-col gap-4">
|
|
96
|
-
{/* Preset Filters */}
|
|
97
|
+
{/* Preset Filters + Refresh */}
|
|
97
98
|
<div className="flex flex-wrap items-center gap-2">
|
|
98
99
|
<span className="text-sm font-medium text-foreground">Filter by:</span>
|
|
99
100
|
{FILTER_PRESETS.map((preset) => (
|
|
@@ -109,6 +110,7 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
109
110
|
{preset.label}
|
|
110
111
|
</button>
|
|
111
112
|
))}
|
|
113
|
+
|
|
112
114
|
</div>
|
|
113
115
|
|
|
114
116
|
{/* Keyword Search */}
|
|
@@ -160,15 +162,13 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
160
162
|
</button>
|
|
161
163
|
</div>
|
|
162
164
|
))}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
</button>
|
|
171
|
-
)}
|
|
165
|
+
<button
|
|
166
|
+
onClick={clearKeywords}
|
|
167
|
+
className="px-2 py-1.5 text-xs bg-muted text-muted-foreground hover:bg-muted/80 rounded-md transition-colors"
|
|
168
|
+
aria-label="Clear all keywords"
|
|
169
|
+
>
|
|
170
|
+
Clear all
|
|
171
|
+
</button>
|
|
172
172
|
</div>
|
|
173
173
|
)}
|
|
174
174
|
</div>
|
|
@@ -192,7 +192,7 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
192
192
|
aria-label="Filter to date"
|
|
193
193
|
/>
|
|
194
194
|
</div>
|
|
195
|
-
{(filterPreset !== "all" || dateRange.from || dateRange.to || keywords.length > 0) && (
|
|
195
|
+
{(filterPreset !== "all" || dateRange.from !== null || dateRange.to !== null || keywords.length > 0) && (
|
|
196
196
|
<button
|
|
197
197
|
onClick={clearFilters}
|
|
198
198
|
className="px-3 py-2 text-sm bg-muted text-muted-foreground hover:bg-muted/80 rounded-md transition-colors"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
"use client";
|
|
7
7
|
|
|
8
|
-
import React, { useRef } from "react";
|
|
8
|
+
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
9
9
|
import { useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
10
10
|
import type { LogEntry } from "@/lib/log-entries";
|
|
11
11
|
import { StatsBar } from "@/app/components/log-viewer/stats-bar";
|
|
@@ -14,6 +14,7 @@ import { EntryRow } from "@/app/components/log-viewer/entry-row";
|
|
|
14
14
|
import EvalResultsPanel from "@/app/components/eval-results-panel";
|
|
15
15
|
import EnrichmentResultsPanel from "@/app/components/enrichment-results-panel";
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
// ── Virtualized Entry List ──
|
|
18
19
|
|
|
19
20
|
interface VirtualizedEntryListProps {
|
|
@@ -27,20 +28,83 @@ interface VirtualizedEntryListProps {
|
|
|
27
28
|
// after render and self-corrects, so these just need to be close enough to
|
|
28
29
|
// avoid large layout jumps on first paint.
|
|
29
30
|
function estimateSize(entry: LogEntry): number {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
switch (entry.type) {
|
|
32
|
+
case "queue-operation":
|
|
33
|
+
return 48;
|
|
34
|
+
case "user":
|
|
35
|
+
return 90;
|
|
36
|
+
case "assistant":
|
|
37
|
+
return 80 + entry.message.content.length * 120;
|
|
38
|
+
default:
|
|
39
|
+
return 100;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type QueueOperationEntry = Extract<LogEntry, { type: "queue-operation" }>;
|
|
44
|
+
|
|
45
|
+
function getSegmentId(entry: QueueOperationEntry): string {
|
|
46
|
+
return `${entry.uuid}-${entry.timestampMs}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Walks the entries array and returns a Map from each queue-operation segment
|
|
51
|
+
* to the count of non-queue-operation entries in its segment (entries after
|
|
52
|
+
* it until the next queue-operation or end of list).
|
|
53
|
+
*/
|
|
54
|
+
function computeSegments(entries: LogEntry[]): Map<string, number> {
|
|
55
|
+
const segments = new Map<string, number>();
|
|
56
|
+
let currentId: string | null = null;
|
|
57
|
+
let count = 0;
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (entry.type === "queue-operation") {
|
|
61
|
+
if (currentId !== null) {
|
|
62
|
+
segments.set(currentId, count);
|
|
63
|
+
}
|
|
64
|
+
currentId = getSegmentId(entry);
|
|
65
|
+
count = 0;
|
|
66
|
+
} else if (currentId !== null) {
|
|
67
|
+
count++;
|
|
68
|
+
}
|
|
34
69
|
}
|
|
35
|
-
|
|
70
|
+
if (currentId !== null) {
|
|
71
|
+
segments.set(currentId, count);
|
|
72
|
+
}
|
|
73
|
+
return segments;
|
|
36
74
|
}
|
|
37
75
|
|
|
38
76
|
function VirtualizedEntryList({ entries, projectName, sessionId }: VirtualizedEntryListProps) {
|
|
39
77
|
const listRef = useRef<HTMLDivElement>(null);
|
|
78
|
+
const [collapsedSessions, setCollapsedSessions] = useState<Set<string>>(new Set());
|
|
79
|
+
|
|
80
|
+
const segments = useMemo(() => computeSegments(entries), [entries]);
|
|
81
|
+
|
|
82
|
+
const visibleEntries = useMemo(() => {
|
|
83
|
+
let currentCollapsed = false;
|
|
84
|
+
return entries.filter((entry) => {
|
|
85
|
+
if (entry.type === "queue-operation") {
|
|
86
|
+
currentCollapsed = collapsedSessions.has(getSegmentId(entry));
|
|
87
|
+
return true; // dividers are always visible
|
|
88
|
+
}
|
|
89
|
+
return !currentCollapsed;
|
|
90
|
+
});
|
|
91
|
+
}, [entries, collapsedSessions]);
|
|
92
|
+
|
|
93
|
+
const handleToggleSegment = useCallback((uuid: string) => {
|
|
94
|
+
setCollapsedSessions((prev) => {
|
|
95
|
+
const next = new Set(prev);
|
|
96
|
+
if (next.has(uuid)) {
|
|
97
|
+
next.delete(uuid);
|
|
98
|
+
} else {
|
|
99
|
+
next.add(uuid);
|
|
100
|
+
}
|
|
101
|
+
return next;
|
|
102
|
+
});
|
|
103
|
+
}, []);
|
|
40
104
|
|
|
41
105
|
const virtualizer = useWindowVirtualizer({
|
|
42
|
-
count:
|
|
43
|
-
estimateSize: (index) => estimateSize(
|
|
106
|
+
count: visibleEntries.length,
|
|
107
|
+
estimateSize: (index) => estimateSize(visibleEntries[index]),
|
|
44
108
|
overscan: 5,
|
|
45
109
|
scrollMargin: listRef.current?.offsetTop ?? 0,
|
|
46
110
|
});
|
|
@@ -55,10 +119,10 @@ function VirtualizedEntryList({ entries, projectName, sessionId }: VirtualizedEn
|
|
|
55
119
|
}}
|
|
56
120
|
>
|
|
57
121
|
{virtualizer.getVirtualItems().map((virtualRow) => {
|
|
58
|
-
const entry =
|
|
122
|
+
const entry = visibleEntries[virtualRow.index];
|
|
59
123
|
return (
|
|
60
124
|
<div
|
|
61
|
-
key={entry.uuid || entry.timestamp}
|
|
125
|
+
key={entry.type === "queue-operation" ? getSegmentId(entry) : (entry.uuid || entry.timestamp)}
|
|
62
126
|
data-index={virtualRow.index}
|
|
63
127
|
ref={virtualizer.measureElement}
|
|
64
128
|
style={{
|
|
@@ -70,7 +134,12 @@ function VirtualizedEntryList({ entries, projectName, sessionId }: VirtualizedEn
|
|
|
70
134
|
}}
|
|
71
135
|
>
|
|
72
136
|
{entry.type === "queue-operation" ? (
|
|
73
|
-
<QueueDivider
|
|
137
|
+
<QueueDivider
|
|
138
|
+
entry={entry}
|
|
139
|
+
isCollapsed={collapsedSessions.has(getSegmentId(entry))}
|
|
140
|
+
entryCount={segments.get(getSegmentId(entry)) ?? 0}
|
|
141
|
+
onToggle={() => handleToggleSegment(getSegmentId(entry))}
|
|
142
|
+
/>
|
|
74
143
|
) : (
|
|
75
144
|
<EntryRow
|
|
76
145
|
entry={entry}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useState, useEffect, useTransition } from "react";
|
|
5
|
+
import { RefreshCw } from "lucide-react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const AUTO_REFRESH_OPTIONS = [
|
|
9
|
+
{ label: "Off", value: 0 },
|
|
10
|
+
{ label: "5s", value: 5 },
|
|
11
|
+
{ label: "10s", value: 10 },
|
|
12
|
+
{ label: "30s", value: 30 },
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
interface RefreshButtonProps {
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function RefreshButton({ className }: RefreshButtonProps) {
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const [isPending, startTransition] = useTransition();
|
|
22
|
+
const [autoInterval, setAutoInterval] = useState(0);
|
|
23
|
+
|
|
24
|
+
function handleRefresh(): void {
|
|
25
|
+
startTransition(() => router.refresh());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (autoInterval <= 0) return;
|
|
30
|
+
const id = setInterval(handleRefresh, autoInterval * 1000);
|
|
31
|
+
return () => clearInterval(id);
|
|
32
|
+
}, [autoInterval, router]);
|
|
33
|
+
|
|
34
|
+
const isAutoActive = autoInterval > 0;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={cn(
|
|
39
|
+
"inline-flex items-center rounded-lg border border-border bg-muted/50 p-0.5 gap-0.5",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
<button
|
|
44
|
+
onClick={handleRefresh}
|
|
45
|
+
title="Refresh"
|
|
46
|
+
className={cn(
|
|
47
|
+
"inline-flex items-center justify-center rounded-md p-1.5 transition-colors",
|
|
48
|
+
isAutoActive
|
|
49
|
+
? "text-primary hover:bg-primary/10"
|
|
50
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
<RefreshCw
|
|
54
|
+
className={cn("w-3.5 h-3.5", isPending && "animate-spin")}
|
|
55
|
+
/>
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<div className="w-px h-4 bg-border" />
|
|
59
|
+
|
|
60
|
+
<div className="inline-flex items-center gap-0.5" role="group" aria-label="Auto-refresh interval">
|
|
61
|
+
{AUTO_REFRESH_OPTIONS.map((opt) => (
|
|
62
|
+
<button
|
|
63
|
+
key={opt.value}
|
|
64
|
+
onClick={() => setAutoInterval(opt.value)}
|
|
65
|
+
aria-pressed={autoInterval === opt.value}
|
|
66
|
+
className={cn(
|
|
67
|
+
"px-2 py-1 text-[11px] font-medium rounded-md transition-colors",
|
|
68
|
+
autoInterval === opt.value
|
|
69
|
+
? "bg-background text-foreground shadow-sm"
|
|
70
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{opt.label}
|
|
74
|
+
</button>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -18,6 +18,8 @@ import { File, Search } from "lucide-react";
|
|
|
18
18
|
import Link from "next/link";
|
|
19
19
|
import PaginationControls from "./pagination-controls";
|
|
20
20
|
import DatePickerInput from "./date-picker-input";
|
|
21
|
+
import { CopyButton } from "./copy-button";
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
interface SessionsListProps {
|
|
23
25
|
files: SessionFile[];
|
|
@@ -56,13 +58,14 @@ export default function SessionsList({ files, projectName }: SessionsListProps)
|
|
|
56
58
|
const endIndex = Math.min(startIndex + ITEMS_PER_PAGE, filteredFiles.length);
|
|
57
59
|
const paginatedFiles = filteredFiles.slice(startIndex, endIndex);
|
|
58
60
|
|
|
59
|
-
const hasActiveFilters =
|
|
61
|
+
const hasActiveFilters =
|
|
62
|
+
filterPreset !== "all" || dateRange.from !== null || dateRange.to !== null || sessionIdFilter !== "";
|
|
60
63
|
|
|
61
64
|
return (
|
|
62
65
|
<div className="space-y-4">
|
|
63
66
|
{/* Filters */}
|
|
64
67
|
<div className="bg-card border border-border rounded-lg p-4 flex flex-col gap-4">
|
|
65
|
-
{/* Preset Filters */}
|
|
68
|
+
{/* Preset Filters + Refresh */}
|
|
66
69
|
<div className="flex flex-wrap items-center gap-2">
|
|
67
70
|
<span className="text-sm font-medium text-foreground">Filter by:</span>
|
|
68
71
|
{FILTER_PRESETS.map((preset) => (
|
|
@@ -78,6 +81,7 @@ export default function SessionsList({ files, projectName }: SessionsListProps)
|
|
|
78
81
|
{preset.label}
|
|
79
82
|
</button>
|
|
80
83
|
))}
|
|
84
|
+
|
|
81
85
|
</div>
|
|
82
86
|
|
|
83
87
|
{/* Custom Date Range */}
|
|
@@ -154,18 +158,23 @@ export default function SessionsList({ files, projectName }: SessionsListProps)
|
|
|
154
158
|
<File className="w-5 h-5 text-primary" />
|
|
155
159
|
</td>
|
|
156
160
|
<td className="px-4 py-3 max-w-md">
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
<div className="flex items-center gap-1">
|
|
162
|
+
{file.sessionId ? (
|
|
163
|
+
<>
|
|
164
|
+
<Link
|
|
165
|
+
href={`/project/${encodeURIComponent(projectName)}/session/${encodeURIComponent(file.sessionId)}`}
|
|
166
|
+
className="font-semibold text-foreground hover:text-primary transition-colors break-words break-all inline-block max-w-full"
|
|
167
|
+
>
|
|
168
|
+
{file.name.replace(/\.jsonl$/, "")}
|
|
169
|
+
</Link>
|
|
170
|
+
<CopyButton text={file.sessionId} />
|
|
171
|
+
</>
|
|
172
|
+
) : (
|
|
173
|
+
<span className="font-semibold text-foreground break-words break-all inline-block max-w-full">
|
|
174
|
+
{file.name.replace(/\.jsonl$/, "")}
|
|
175
|
+
</span>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
169
178
|
</td>
|
|
170
179
|
<td className="px-4 py-3 text-sm text-muted-foreground">
|
|
171
180
|
{file.lastModifiedFormatted || formatDate(file.lastModified)}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Session page — parses and displays a single session's JSONL log via the Raw Log Viewer. */
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import { ArrowLeft } from "lucide-react";
|
|
3
|
+
import { ArrowLeft, Download } from "lucide-react";
|
|
4
4
|
import { getCachedSessionLog } from "@/lib/log-entries";
|
|
5
5
|
import { decodeFolderName } from "@/lib/paths";
|
|
6
6
|
import LazyLogViewer from "@/app/components/lazy-log-viewer";
|
|
7
|
+
import { CopyButton } from "@/app/components/copy-button";
|
|
7
8
|
|
|
8
9
|
export const dynamic = "force-dynamic";
|
|
9
10
|
|
|
@@ -25,10 +26,8 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
25
26
|
try {
|
|
26
27
|
entries = await getCachedSessionLog(decodedName, decodedSessionId);
|
|
27
28
|
} catch (e) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
? "Session log file not found."
|
|
31
|
-
: "Failed to read session log.";
|
|
29
|
+
const isNotFound = (e as NodeJS.ErrnoException).code === "ENOENT";
|
|
30
|
+
error = isNotFound ? "Session log file not found." : "Failed to read session log.";
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
return (
|
|
@@ -51,24 +50,36 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
51
50
|
<span className="font-medium">Project:</span>{" "}
|
|
52
51
|
{decodeFolderName(decodedName)}
|
|
53
52
|
</p>
|
|
54
|
-
<p className="text-muted-foreground break-words break-all">
|
|
53
|
+
<p className="text-muted-foreground break-words break-all inline-flex items-center gap-1">
|
|
55
54
|
<span className="font-medium">Session:</span> {decodedSessionId}
|
|
55
|
+
<CopyButton text={decodedSessionId} />
|
|
56
56
|
</p>
|
|
57
57
|
{entries && (
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
58
|
+
<div className="flex items-center gap-4">
|
|
59
|
+
<p className="text-muted-foreground">
|
|
60
|
+
<span className="font-medium">{entries.length}</span> log entries
|
|
61
|
+
</p>
|
|
62
|
+
<a
|
|
63
|
+
href={`/api/download/${encodeURIComponent(decodedName)}/${encodeURIComponent(decodedSessionId)}`}
|
|
64
|
+
download
|
|
65
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors"
|
|
66
|
+
>
|
|
67
|
+
<Download className="w-4 h-4" />
|
|
68
|
+
Download JSONL
|
|
69
|
+
</a>
|
|
70
|
+
</div>
|
|
61
71
|
)}
|
|
62
72
|
</div>
|
|
63
73
|
</div>
|
|
64
74
|
|
|
65
|
-
{error
|
|
75
|
+
{error && (
|
|
66
76
|
<div className="bg-card text-card-foreground rounded-lg border border-destructive/50 p-6 shadow-sm">
|
|
67
77
|
<p className="text-destructive text-center py-8">{error}</p>
|
|
68
78
|
</div>
|
|
69
|
-
)
|
|
79
|
+
)}
|
|
80
|
+
{!error && entries && (
|
|
70
81
|
<LazyLogViewer entries={entries} projectName={decodedName} sessionId={decodedSessionId} />
|
|
71
|
-
)
|
|
82
|
+
)}
|
|
72
83
|
</div>
|
|
73
84
|
</main>
|
|
74
85
|
);
|