@sentry/junior-dashboard 0.276.0 → 0.278.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/dist/app.js +196 -72
- package/dist/client.js +94 -89
- package/dist/index.d.ts +1 -1
- package/dist/index.js +191 -70
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +20 -2
- package/src/app.ts +80 -12
- package/src/auth.ts +5 -1
- package/src/client/App.tsx +9 -4
- package/src/client/components/DetailsPanel.tsx +1 -1
- package/src/client/components/FormNotice.tsx +11 -14
- package/src/client/components/ProfileMenu.tsx +5 -6
- package/src/client/components/ui/resizable.tsx +50 -0
- package/src/client/conversations/AutoArchiveToggle.tsx +71 -0
- package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
- package/src/client/conversations/ConversationChildAgents.tsx +2 -1
- package/src/client/conversations/ConversationDetails.tsx +6 -3
- package/src/client/conversations/ConversationHeader.tsx +15 -2
- package/src/client/conversations/ConversationHeaderActions.tsx +27 -0
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationMemories.tsx +64 -67
- package/src/client/conversations/ConversationMeta.tsx +2 -7
- package/src/client/conversations/ConversationPage.tsx +175 -79
- package/src/client/conversations/ConversationSidebar.tsx +82 -25
- package/src/client/conversations/ConversationTranscript.tsx +11 -8
- package/src/client/conversations/ConversationWatches.tsx +15 -21
- package/src/client/conversations/ConversationWorkspace.tsx +132 -44
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +17 -3
- package/src/client/conversations/DetailsList.tsx +8 -14
- package/src/client/conversations/EventDetails.tsx +2 -62
- package/src/client/conversations/MemoryIcons.tsx +23 -0
- package/src/client/conversations/TranscriptActivityGroup.tsx +36 -14
- package/src/client/conversations/TranscriptContextEventView.tsx +7 -18
- package/src/client/conversations/TranscriptMemoryPopover.tsx +20 -25
- package/src/client/conversations/TranscriptRailEvent.tsx +0 -8
- package/src/client/conversations/TranscriptTurnContextView.tsx +9 -5
- package/src/client/conversations/TranscriptView.tsx +89 -20
- package/src/client/conversations/autoArchiveQueries.ts +53 -0
- package/src/client/conversations/conversationArchive.ts +255 -0
- package/src/client/conversations/conversationMemory.ts +2 -1
- package/src/client/conversations/conversationSections.ts +24 -18
- package/src/client/conversations/conversationSidebarLayout.ts +61 -0
- package/src/client/conversations/eventLog.ts +2 -31
- package/src/client/conversations/eventTranscript.ts +9 -42
- package/src/client/conversations/queries.ts +121 -250
- package/src/client/conversations/transcript.ts +46 -16
- package/src/client/conversations/transcriptBottomPinning.ts +40 -24
- package/src/client/conversations/transcriptReadPosition.ts +31 -7
- package/src/client/conversations/transcriptRenderModel.ts +1 -14
- package/src/client/conversations/transcriptSearch.tsx +3 -11
- package/src/client/conversations/transcriptUnreadHistory.ts +248 -0
- package/src/client/conversations/usePinConversation.ts +88 -0
- package/src/client/display-text/DisplayText.tsx +7 -2
- package/src/client/display-text/codeBlocks.ts +92 -0
- package/src/client/format.ts +3 -67
- package/src/client/http.ts +5 -5
- package/src/client/markdownExport.ts +32 -42
- package/src/client/pages/code/RepositoryTabs.tsx +2 -2
- package/src/client/pages/memory/MemoryDetails.tsx +6 -33
- package/src/client/pages/memory/MemoryPage.tsx +7 -92
- package/src/client/pages/memory/MemoryTimeline.tsx +16 -32
- package/src/client/pages/memory/memoryDashboard.ts +2 -38
- package/src/client/pages/memory/memoryRecord.ts +1 -6
- package/src/client/pages/settings/AutoArchiveSettings.tsx +145 -0
- package/src/client/pages/settings/SettingsPage.tsx +2 -0
- package/src/client/review-desk/ConversationReviewItems.tsx +7 -9
- package/src/client/review-desk/queries.ts +2 -1
- package/src/client/types.ts +19 -33
- package/src/conversation-cache.ts +7 -9
- package/src/mock-reporting/fixtures.ts +126 -60
- package/src/mock-reporting/routes.ts +50 -1
- package/src/tailwind.css +30 -0
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
ActivityChartXAxis,
|
|
6
6
|
ActivityChartYAxis,
|
|
7
7
|
} from "../../components/charts/ActivityChart";
|
|
8
|
-
import { ChartLegend } from "../../components/charts/ChartLegend";
|
|
9
8
|
import {
|
|
10
9
|
type TimeRangeBucketUnit,
|
|
11
10
|
type TimeRangeDays,
|
|
@@ -14,16 +13,11 @@ import { Card } from "../../components/layout/Card";
|
|
|
14
13
|
import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
|
|
15
14
|
import type { MemoryDay } from "./memoryDashboard";
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
{ color: "var(--color-
|
|
19
|
-
|
|
20
|
-
] as const;
|
|
16
|
+
const chartConfig = {
|
|
17
|
+
count: { color: "var(--color-emerald-300)", label: "Memories" },
|
|
18
|
+
};
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
series.map((item) => [item.key, { color: item.color, label: item.label }]),
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
/** Render viewer memory creation as a stacked personal/public timeline. */
|
|
20
|
+
/** Render memory creation as a timeline. */
|
|
27
21
|
export function MemoryTimeline(props: {
|
|
28
22
|
bucketUnit?: TimeRangeBucketUnit;
|
|
29
23
|
|
|
@@ -33,7 +27,7 @@ export function MemoryTimeline(props: {
|
|
|
33
27
|
const bucketUnit = props.bucketUnit ?? "day";
|
|
34
28
|
|
|
35
29
|
const days = props.days;
|
|
36
|
-
const totals = days.map((day) => day.
|
|
30
|
+
const totals = days.map((day) => day.count);
|
|
37
31
|
const maximum = Math.max(1, ...totals);
|
|
38
32
|
const hasMemories = totals.some((total) => total > 0);
|
|
39
33
|
|
|
@@ -45,15 +39,13 @@ export function MemoryTimeline(props: {
|
|
|
45
39
|
</h2>
|
|
46
40
|
<p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
|
|
47
41
|
{bucketUnit === "hour"
|
|
48
|
-
? "
|
|
42
|
+
? "Memories created each hour."
|
|
49
43
|
: bucketUnit === "6hour"
|
|
50
|
-
? "
|
|
51
|
-
: "
|
|
44
|
+
? "Memories created each 6 hours."
|
|
45
|
+
: "Memories created each day."}
|
|
52
46
|
</p>
|
|
53
47
|
</div>
|
|
54
48
|
|
|
55
|
-
<ChartLegend ariaLabel="Memory visibility legend" items={series} />
|
|
56
|
-
|
|
57
49
|
<div className="relative mt-3 overflow-hidden">
|
|
58
50
|
<ChartContainer
|
|
59
51
|
className="aspect-auto h-50 w-full"
|
|
@@ -71,25 +63,17 @@ export function MemoryTimeline(props: {
|
|
|
71
63
|
<ChartTooltip
|
|
72
64
|
content={
|
|
73
65
|
<ActivityChartTooltipContent
|
|
74
|
-
rows={(day: MemoryDay) => [
|
|
75
|
-
["personal", day.personal],
|
|
76
|
-
["public", day.public],
|
|
77
|
-
["total", day.personal + day.public],
|
|
78
|
-
]}
|
|
66
|
+
rows={(day: MemoryDay) => [["memories", day.count]]}
|
|
79
67
|
/>
|
|
80
68
|
}
|
|
81
69
|
/>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
maxBarSize={13}
|
|
90
|
-
stackId="visibility"
|
|
91
|
-
/>
|
|
92
|
-
))}
|
|
70
|
+
<Bar
|
|
71
|
+
dataKey="count"
|
|
72
|
+
fill="var(--color-count)"
|
|
73
|
+
fillOpacity={0.82}
|
|
74
|
+
isAnimationActive={false}
|
|
75
|
+
maxBarSize={13}
|
|
76
|
+
/>
|
|
93
77
|
</BarChart>
|
|
94
78
|
</ChartContainer>
|
|
95
79
|
{!hasMemories ? (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import { fetchDashboardJson } from "../../http";
|
|
@@ -7,9 +7,8 @@ const memoryBucketSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2})?$/);
|
|
|
7
7
|
|
|
8
8
|
const memoryDaySchema = z
|
|
9
9
|
.object({
|
|
10
|
+
count: z.number().int().min(0),
|
|
10
11
|
date: memoryBucketSchema,
|
|
11
|
-
personal: z.number().int().min(0),
|
|
12
|
-
public: z.number().int().min(0),
|
|
13
12
|
})
|
|
14
13
|
.strict();
|
|
15
14
|
|
|
@@ -38,10 +37,8 @@ export const memoryDashboardSchema = z
|
|
|
38
37
|
embedded: z.number().int().min(0),
|
|
39
38
|
explicit: z.number().int().min(0),
|
|
40
39
|
knowledge: z.number().int().min(0),
|
|
41
|
-
personal: z.number().int().min(0),
|
|
42
40
|
preference: z.number().int().min(0),
|
|
43
41
|
procedure: z.number().int().min(0),
|
|
44
|
-
public: z.number().int().min(0),
|
|
45
42
|
})
|
|
46
43
|
.strict(),
|
|
47
44
|
})
|
|
@@ -64,36 +61,3 @@ export function useMemoryDashboardData() {
|
|
|
64
61
|
retry: false,
|
|
65
62
|
});
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
const memoryCountsSchema = z
|
|
69
|
-
.object({
|
|
70
|
-
active: z.number().int().min(0),
|
|
71
|
-
personal: z.number().int().min(0),
|
|
72
|
-
public: z.number().int().min(0),
|
|
73
|
-
})
|
|
74
|
-
.strict();
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Load viewer-scoped memory totals that match a search query. The query stays
|
|
78
|
-
* idle without a search, because the dashboard summary owns unfiltered totals.
|
|
79
|
-
*/
|
|
80
|
-
export function useMemorySearchCounts(searchQuery: string) {
|
|
81
|
-
return useQuery({
|
|
82
|
-
enabled: Boolean(searchQuery),
|
|
83
|
-
placeholderData: keepPreviousData,
|
|
84
|
-
queryFn: ({ signal }) =>
|
|
85
|
-
fetchDashboardJson(
|
|
86
|
-
memoryCountsSchema,
|
|
87
|
-
`/api/plugins/memory/counts?${new URLSearchParams({ q: searchQuery })}`,
|
|
88
|
-
signal,
|
|
89
|
-
),
|
|
90
|
-
queryKey: [
|
|
91
|
-
"dashboard",
|
|
92
|
-
"plugin-user-page",
|
|
93
|
-
"memory",
|
|
94
|
-
"counts",
|
|
95
|
-
searchQuery,
|
|
96
|
-
],
|
|
97
|
-
retry: false,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -14,7 +14,6 @@ const memoryRecordSchema = z
|
|
|
14
14
|
observedAt: z.iso.datetime(),
|
|
15
15
|
origin: z.enum(["automatic", "explicit", "other"]),
|
|
16
16
|
sourcePlatform: z.enum(["local", "slack", "web"]),
|
|
17
|
-
visibility: z.enum(["private", "public"]),
|
|
18
17
|
})
|
|
19
18
|
.strict();
|
|
20
19
|
|
|
@@ -43,10 +42,7 @@ export function memoryPageRecord(memory: MemoryRecord): PluginUserPageRecord {
|
|
|
43
42
|
return {
|
|
44
43
|
actions: [
|
|
45
44
|
{
|
|
46
|
-
confirmation:
|
|
47
|
-
memory.visibility === "public"
|
|
48
|
-
? "Forget this memory for everyone?"
|
|
49
|
-
: "Forget this memory?",
|
|
45
|
+
confirmation: "Forget this memory for everyone?",
|
|
50
46
|
href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`,
|
|
51
47
|
label: "Forget",
|
|
52
48
|
method: "DELETE",
|
|
@@ -58,7 +54,6 @@ export function memoryPageRecord(memory: MemoryRecord): PluginUserPageRecord {
|
|
|
58
54
|
{ label: "Type", value: titleCase(memory.kind) },
|
|
59
55
|
{ label: "Learned", value: learned },
|
|
60
56
|
{ label: "Source", value: titleCase(memory.sourcePlatform) },
|
|
61
|
-
{ label: "Visibility", value: titleCase(memory.visibility) },
|
|
62
57
|
{ label: "Remembered", value: formattedDate(memory.createdAt) },
|
|
63
58
|
{ label: "Observed", value: formattedDate(memory.observedAt) },
|
|
64
59
|
{
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AUTO_ARCHIVE_MAX_GRACE_HOURS,
|
|
5
|
+
dashboardIdentitySchema,
|
|
6
|
+
type DashboardIdentity,
|
|
7
|
+
} from "../../../api/schema";
|
|
8
|
+
import { Button } from "../../components/ui/button";
|
|
9
|
+
import { Checkbox } from "../../components/ui/checkbox";
|
|
10
|
+
import { Field, FieldDescription, FieldLabel } from "../../components/ui/field";
|
|
11
|
+
import { NativeSelect } from "../../components/ui/native-select";
|
|
12
|
+
import { InlineError } from "../../components/InlineError";
|
|
13
|
+
import { Card } from "../../components/layout/Card";
|
|
14
|
+
import { useArchiveDoneConversations } from "../../conversations/autoArchiveQueries";
|
|
15
|
+
import { patch } from "../../http";
|
|
16
|
+
import type { DashboardCoreData } from "../../types";
|
|
17
|
+
|
|
18
|
+
const dashboardCoreQueryKey = ["dashboard", "core"] as const;
|
|
19
|
+
|
|
20
|
+
/** Grace periods offered in Settings, in hours. */
|
|
21
|
+
const AUTO_ARCHIVE_GRACE_OPTIONS = [1, 6, 24, 72, AUTO_ARCHIVE_MAX_GRACE_HOURS];
|
|
22
|
+
|
|
23
|
+
function formatArchivedCount(count: number): string {
|
|
24
|
+
if (count === 0) return "No done conversations to archive.";
|
|
25
|
+
return count === 1
|
|
26
|
+
? "Archived 1 conversation."
|
|
27
|
+
: `Archived ${count} conversations.`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatGraceHours(hours: number): string {
|
|
31
|
+
if (hours % 24 !== 0) return hours === 1 ? "1 hour" : `${hours} hours`;
|
|
32
|
+
const days = hours / 24;
|
|
33
|
+
if (days === 7) return "1 week";
|
|
34
|
+
return days === 1 ? "1 day" : `${days} days`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Let the user turn auto-archive on for their conversations and pick how long
|
|
39
|
+
* finished work must stay quiet before Junior archives it.
|
|
40
|
+
*/
|
|
41
|
+
export function AutoArchiveSettings({
|
|
42
|
+
identity,
|
|
43
|
+
}: {
|
|
44
|
+
identity: DashboardIdentity;
|
|
45
|
+
}) {
|
|
46
|
+
const queryClient = useQueryClient();
|
|
47
|
+
const update = useMutation({
|
|
48
|
+
mutationFn: (
|
|
49
|
+
settings: Pick<
|
|
50
|
+
DashboardIdentity,
|
|
51
|
+
"autoArchiveDefault" | "autoArchiveGraceHours"
|
|
52
|
+
>,
|
|
53
|
+
) => patch(dashboardIdentitySchema, "/api/me", settings),
|
|
54
|
+
onSuccess: (updated) => {
|
|
55
|
+
queryClient.setQueryData<DashboardCoreData>(
|
|
56
|
+
dashboardCoreQueryKey,
|
|
57
|
+
(current) => (current ? { ...current, me: updated } : current),
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const archiveDone = useArchiveDoneConversations();
|
|
62
|
+
const enabled = identity.autoArchiveDefault ?? false;
|
|
63
|
+
const graceHours = identity.autoArchiveGraceHours ?? 24;
|
|
64
|
+
// Keep a saved value that is not a preset, so the select shows the truth.
|
|
65
|
+
const graceOptions = AUTO_ARCHIVE_GRACE_OPTIONS.includes(graceHours)
|
|
66
|
+
? AUTO_ARCHIVE_GRACE_OPTIONS
|
|
67
|
+
: [...AUTO_ARCHIVE_GRACE_OPTIONS, graceHours].sort((a, b) => a - b);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Card className="mt-6 mb-0" padding="md" variant="raised">
|
|
71
|
+
<h2 className="m-0 text-lg font-bold">Conversations</h2>
|
|
72
|
+
<label className="mt-4 flex cursor-pointer items-start gap-3">
|
|
73
|
+
<Checkbox
|
|
74
|
+
checked={enabled}
|
|
75
|
+
className="mt-0.5"
|
|
76
|
+
disabled={update.isPending}
|
|
77
|
+
onCheckedChange={(checked) =>
|
|
78
|
+
update.mutate({ autoArchiveDefault: checked })
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
<span className="grid gap-0.5">
|
|
82
|
+
<span className="text-sm font-medium">
|
|
83
|
+
Auto-archive conversations when done
|
|
84
|
+
</span>
|
|
85
|
+
<span className="text-xs text-dashboard-text-muted">
|
|
86
|
+
Archive a conversation for you when all of its linked work, such as
|
|
87
|
+
pull requests, is merged or closed and nobody replies during the
|
|
88
|
+
wait time. You can change this for each conversation in its details.
|
|
89
|
+
</span>
|
|
90
|
+
</span>
|
|
91
|
+
</label>
|
|
92
|
+
<Field className="mt-5 max-w-xs">
|
|
93
|
+
<FieldLabel htmlFor="auto-archive-grace">Wait time</FieldLabel>
|
|
94
|
+
<NativeSelect
|
|
95
|
+
aria-describedby="auto-archive-grace-help"
|
|
96
|
+
disabled={update.isPending}
|
|
97
|
+
id="auto-archive-grace"
|
|
98
|
+
onChange={(event) =>
|
|
99
|
+
update.mutate({ autoArchiveGraceHours: Number(event.target.value) })
|
|
100
|
+
}
|
|
101
|
+
value={graceHours}
|
|
102
|
+
>
|
|
103
|
+
{graceOptions.map((hours) => (
|
|
104
|
+
<option key={hours} value={hours}>
|
|
105
|
+
{formatGraceHours(hours)}
|
|
106
|
+
</option>
|
|
107
|
+
))}
|
|
108
|
+
</NativeSelect>
|
|
109
|
+
<FieldDescription id="auto-archive-grace-help">
|
|
110
|
+
How long finished work and the conversation stay quiet before archive.
|
|
111
|
+
</FieldDescription>
|
|
112
|
+
</Field>
|
|
113
|
+
{update.isError ? (
|
|
114
|
+
<InlineError className="mt-3">
|
|
115
|
+
Could not save this setting. Try again.
|
|
116
|
+
</InlineError>
|
|
117
|
+
) : null}
|
|
118
|
+
<div className="mt-5 grid gap-1.5">
|
|
119
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
120
|
+
<Button
|
|
121
|
+
disabled={archiveDone.isPending}
|
|
122
|
+
onClick={() => archiveDone.mutate()}
|
|
123
|
+
type="button"
|
|
124
|
+
>
|
|
125
|
+
{archiveDone.isPending ? "Archiving…" : "Archive done now"}
|
|
126
|
+
</Button>
|
|
127
|
+
{archiveDone.isSuccess ? (
|
|
128
|
+
<p className="m-0 text-sm text-dashboard-text-muted" role="status">
|
|
129
|
+
{formatArchivedCount(archiveDone.data.archived)}
|
|
130
|
+
</p>
|
|
131
|
+
) : null}
|
|
132
|
+
{archiveDone.isError ? (
|
|
133
|
+
<InlineError>
|
|
134
|
+
Could not archive conversations. Try again.
|
|
135
|
+
</InlineError>
|
|
136
|
+
) : null}
|
|
137
|
+
</div>
|
|
138
|
+
<p className="m-0 text-xs text-dashboard-text-muted">
|
|
139
|
+
Archive every done conversation once, without the wait time. Skips
|
|
140
|
+
conversations where you turned auto-archive off.
|
|
141
|
+
</p>
|
|
142
|
+
</div>
|
|
143
|
+
</Card>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -24,6 +24,7 @@ import { InlineError } from "../../components/InlineError";
|
|
|
24
24
|
import { Card } from "../../components/layout/Card";
|
|
25
25
|
import { DashboardApiError, fetchDashboardJson, patch, put } from "../../http";
|
|
26
26
|
import type { DashboardCoreData } from "../../types";
|
|
27
|
+
import { AutoArchiveSettings } from "./AutoArchiveSettings";
|
|
27
28
|
import { SettingsPageLayout } from "./SettingsPageLayout";
|
|
28
29
|
|
|
29
30
|
const dashboardCoreQueryKey = ["dashboard", "core"] as const;
|
|
@@ -108,6 +109,7 @@ export function SettingsPage({ identity }: SettingsPageProps) {
|
|
|
108
109
|
</form>
|
|
109
110
|
<ConversationStartSetting />
|
|
110
111
|
<ComposerAutocorrectSetting />
|
|
112
|
+
<AutoArchiveSettings identity={identity} />
|
|
111
113
|
<CustomizationForm />
|
|
112
114
|
</SettingsPageLayout>
|
|
113
115
|
);
|
|
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import type { ReviewItemPayload } from "@sentry/junior/api/schema";
|
|
4
4
|
|
|
5
5
|
import { LoadMorePagination } from "../components/Pagination";
|
|
6
|
+
import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
|
|
6
7
|
import {
|
|
7
8
|
DetailsCard,
|
|
8
9
|
DetailsList,
|
|
@@ -74,9 +75,9 @@ function ReviewItemRow(props: {
|
|
|
74
75
|
if (props.defaultExpanded) setExpanded(true);
|
|
75
76
|
}, [props.defaultExpanded]);
|
|
76
77
|
return (
|
|
77
|
-
|
|
78
|
+
<Collapsible className="min-w-0" onOpenChange={setExpanded} open={expanded}>
|
|
78
79
|
<DetailsRow
|
|
79
|
-
|
|
80
|
+
disclosure
|
|
80
81
|
icon={<Inbox aria-hidden="true" className="size-3.5" />}
|
|
81
82
|
meta={
|
|
82
83
|
<span className="grid size-5 place-items-center">
|
|
@@ -86,14 +87,11 @@ function ReviewItemRow(props: {
|
|
|
86
87
|
/>
|
|
87
88
|
</span>
|
|
88
89
|
}
|
|
89
|
-
onToggle={() => setExpanded((value) => !value)}
|
|
90
90
|
title={props.item.title}
|
|
91
91
|
/>
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
) : null}
|
|
97
|
-
</>
|
|
92
|
+
<CollapsibleContent className="px-1.5 pb-1">
|
|
93
|
+
<ReviewItemCard item={props.item} />
|
|
94
|
+
</CollapsibleContent>
|
|
95
|
+
</Collapsible>
|
|
98
96
|
);
|
|
99
97
|
}
|
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
import { conversationDetailTitle } from "../format";
|
|
21
21
|
import { fetchDashboardJson, post } from "../http";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** Prefix of every Review item read, for refreshes after a change. */
|
|
24
|
+
export const REVIEW_ITEMS_QUERY_KEY = ["dashboard", "review-items"] as const;
|
|
24
25
|
const COUNT_REFRESH_MS = 60_000;
|
|
25
26
|
|
|
26
27
|
/**
|
package/src/client/types.ts
CHANGED
|
@@ -56,32 +56,22 @@ export type TranscriptViewSubagentPart = {
|
|
|
56
56
|
export type TranscriptViewContextEventPart = {
|
|
57
57
|
/** People mentioned in the summary, resolved by the API. */
|
|
58
58
|
mentions?: ActorIdentity[];
|
|
59
|
-
event:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
| {
|
|
78
|
-
createdAt: string;
|
|
79
|
-
modelId: string;
|
|
80
|
-
modelProfile: string;
|
|
81
|
-
reasoningLevel?: string;
|
|
82
|
-
summary?: string;
|
|
83
|
-
type: "handoff";
|
|
84
|
-
};
|
|
59
|
+
event: {
|
|
60
|
+
createdAt: string;
|
|
61
|
+
type: "compaction";
|
|
62
|
+
modelId?: string;
|
|
63
|
+
summary?: string;
|
|
64
|
+
details?: {
|
|
65
|
+
reason: "capacity";
|
|
66
|
+
estimatedInputTokens: number;
|
|
67
|
+
replacementInputTokens?: number;
|
|
68
|
+
triggerTokens: number;
|
|
69
|
+
inputLimitTokens: number;
|
|
70
|
+
inputMessageCount: number;
|
|
71
|
+
retainedMessageCount: number;
|
|
72
|
+
summaryChars: number;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
85
75
|
type: "context_event";
|
|
86
76
|
};
|
|
87
77
|
|
|
@@ -138,6 +128,8 @@ export type TranscriptViewMessage = {
|
|
|
138
128
|
events?: ConversationReportMessageEvent[];
|
|
139
129
|
/** Whether the source message addressed Junior directly. */
|
|
140
130
|
explicitMention?: boolean;
|
|
131
|
+
/** Quiet message: stored, but not delivered to the Location. */
|
|
132
|
+
quiet?: true;
|
|
141
133
|
/** Person changes to Review items that this message records. */
|
|
142
134
|
reviewItemChanges?: ReviewItemChangeRecord[];
|
|
143
135
|
/** People mentioned in the event text, resolved by the API. */
|
|
@@ -148,13 +140,6 @@ export type TranscriptViewMessage = {
|
|
|
148
140
|
messageId?: string;
|
|
149
141
|
/** True while the message is accepted in the mailbox but not yet in history. */
|
|
150
142
|
pending?: boolean;
|
|
151
|
-
route?: {
|
|
152
|
-
confidence?: number;
|
|
153
|
-
modelId: string;
|
|
154
|
-
modelProfile: string;
|
|
155
|
-
reasoningLevel: string;
|
|
156
|
-
source: "configured" | "inherited" | "router";
|
|
157
|
-
};
|
|
158
143
|
eventId?: string;
|
|
159
144
|
failureCode?: ConversationTurnFailureCode;
|
|
160
145
|
failureReason?: ConversationTurnFailureReason;
|
|
@@ -176,6 +161,7 @@ export type Conversation = {
|
|
|
176
161
|
annotations?: ConversationSummaryReport["annotations"];
|
|
177
162
|
sidebarAnnotations?: ConversationSummaryReport["sidebarAnnotations"];
|
|
178
163
|
archivedAt?: string | null;
|
|
164
|
+
pinnedAt?: string | null;
|
|
179
165
|
auxiliaryCosts?: ConversationSummaryReport["auxiliaryCosts"];
|
|
180
166
|
channel?: string;
|
|
181
167
|
channelName?: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { CONVERSATION_DETAIL_VERSION_HEADER } from "@sentry/junior/api/schema";
|
|
2
3
|
import type { MiddlewareHandler } from "hono";
|
|
3
4
|
|
|
4
|
-
/** Revalidate
|
|
5
|
+
/** Revalidate mock detail reads on content, after the route checks viewer access. */
|
|
5
6
|
export const revalidateConversation: MiddlewareHandler = async (
|
|
6
7
|
context,
|
|
7
8
|
next,
|
|
@@ -15,15 +16,12 @@ export const revalidateConversation: MiddlewareHandler = async (
|
|
|
15
16
|
// generatedAt is the read time, not a change to the Conversation. The mailbox
|
|
16
17
|
// has its own live watermark and is never handled by this middleware.
|
|
17
18
|
const { generatedAt: _generatedAt, ...content } = report;
|
|
18
|
-
const
|
|
19
|
+
const version = createHash("sha256")
|
|
20
|
+
.update(JSON.stringify(content))
|
|
21
|
+
.digest("hex");
|
|
19
22
|
context.header("cache-control", "private, no-store");
|
|
20
|
-
context.header(
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
validators?.some(
|
|
24
|
-
(value) => value.trim().replace(/^W\//, "") === etag.slice(2),
|
|
25
|
-
)
|
|
26
|
-
) {
|
|
23
|
+
context.header(CONVERSATION_DETAIL_VERSION_HEADER, version);
|
|
24
|
+
if (context.req.header(CONVERSATION_DETAIL_VERSION_HEADER) === version) {
|
|
27
25
|
context.res = new Response(null, {
|
|
28
26
|
status: 304,
|
|
29
27
|
headers: context.res.headers,
|