@sentry/junior-dashboard 0.265.0 → 0.267.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.
Files changed (104) hide show
  1. package/dist/app.js +49 -12
  2. package/dist/client.js +94 -63
  3. package/dist/index.js +49 -12
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +11 -5
  6. package/src/app.ts +0 -2
  7. package/src/auth.ts +4 -7
  8. package/src/client/App.tsx +35 -53
  9. package/src/client/components/ActorAvatar.tsx +10 -22
  10. package/src/client/components/ConnectionBanner.tsx +4 -3
  11. package/src/client/components/CopyButton.tsx +3 -4
  12. package/src/client/components/DetailsPanel.tsx +12 -13
  13. package/src/client/components/Drawer.tsx +46 -23
  14. package/src/client/components/EmptyTelemetry.tsx +5 -3
  15. package/src/client/components/ImageAttachment.tsx +49 -63
  16. package/src/client/components/InlineError.tsx +8 -4
  17. package/src/client/components/Notice.tsx +19 -26
  18. package/src/client/components/Pagination.tsx +26 -26
  19. package/src/client/components/ParticipantAvatarStack.tsx +13 -10
  20. package/src/client/components/README.md +28 -3
  21. package/src/client/components/SearchInput.tsx +51 -41
  22. package/src/client/components/VersionDriftBanner.tsx +9 -7
  23. package/src/client/components/charts/CacheWrites.tsx +52 -43
  24. package/src/client/components/ui/alert.tsx +64 -0
  25. package/src/client/components/ui/avatar.tsx +53 -0
  26. package/src/client/components/ui/button-group.tsx +59 -0
  27. package/src/client/components/ui/button.tsx +2 -1
  28. package/src/client/components/ui/dialog.tsx +60 -0
  29. package/src/client/components/ui/drawer.tsx +140 -0
  30. package/src/client/components/ui/empty.tsx +61 -0
  31. package/src/client/components/ui/input-group.tsx +96 -0
  32. package/src/client/components/ui/pagination.tsx +60 -0
  33. package/src/client/components/ui/separator.tsx +22 -0
  34. package/src/client/components/ui/spinner.tsx +21 -0
  35. package/src/client/components/ui/table.tsx +22 -1
  36. package/src/client/components/ui/tooltip.tsx +1 -1
  37. package/src/client/components/ui/useSheet.ts +5 -1
  38. package/src/client/conversations/ComposerImages.tsx +66 -14
  39. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  40. package/src/client/conversations/ConversationComposer.tsx +84 -12
  41. package/src/client/conversations/ConversationDetails.tsx +3 -27
  42. package/src/client/conversations/ConversationEventDetails.tsx +26 -10
  43. package/src/client/conversations/ConversationHeader.tsx +5 -4
  44. package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
  45. package/src/client/conversations/ConversationPage.tsx +156 -2
  46. package/src/client/conversations/ConversationSidebar.tsx +5 -4
  47. package/src/client/conversations/ConversationTranscript.tsx +48 -16
  48. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  49. package/src/client/conversations/ConversationWatches.tsx +67 -59
  50. package/src/client/conversations/DetailsList.tsx +77 -29
  51. package/src/client/conversations/EventDetails.tsx +46 -19
  52. package/src/client/conversations/EventUsage.tsx +99 -74
  53. package/src/client/conversations/ForkConversationButton.tsx +79 -73
  54. package/src/client/conversations/PendingMailboxStack.tsx +312 -119
  55. package/src/client/conversations/README.md +30 -10
  56. package/src/client/conversations/ShareConversationButton.tsx +3 -4
  57. package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
  58. package/src/client/conversations/TranscriptActivityGroup.tsx +22 -19
  59. package/src/client/conversations/TranscriptAttachment.tsx +39 -4
  60. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
  61. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  62. package/src/client/conversations/TranscriptFailureView.tsx +10 -13
  63. package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
  64. package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
  65. package/src/client/conversations/TranscriptMessageView.tsx +7 -1
  66. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  67. package/src/client/conversations/TranscriptSummary.tsx +13 -7
  68. package/src/client/conversations/TranscriptTurnContextView.tsx +78 -56
  69. package/src/client/conversations/TranscriptView.tsx +7 -3
  70. package/src/client/conversations/UserInputRequestCard.tsx +244 -0
  71. package/src/client/conversations/conversationOutbox.ts +46 -0
  72. package/src/client/conversations/eventLog.ts +1 -0
  73. package/src/client/conversations/eventTime.ts +13 -1
  74. package/src/client/conversations/eventTranscript.ts +12 -5
  75. package/src/client/conversations/queries.ts +127 -26
  76. package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
  77. package/src/client/conversations/transcriptMentions.tsx +18 -5
  78. package/src/client/conversations/userInputRequest.ts +56 -0
  79. package/src/client/format.ts +10 -3
  80. package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
  81. package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
  82. package/src/client/pages/automations/AutomationEditor.tsx +182 -156
  83. package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
  84. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
  85. package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
  86. package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
  87. package/src/client/pages/automations/AutomationOverview.tsx +238 -0
  88. package/src/client/pages/automations/AutomationPage.tsx +343 -0
  89. package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
  90. package/src/client/pages/automations/AutomationRow.tsx +11 -13
  91. package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
  92. package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
  93. package/src/client/pages/automations/AutomationStatus.tsx +164 -108
  94. package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
  95. package/src/client/pages/automations/AutomationsPage.tsx +10 -98
  96. package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
  97. package/src/client/pages/automations/README.md +56 -46
  98. package/src/client/types.ts +3 -0
  99. package/src/mock-reporting/fixtures.ts +44 -2
  100. package/src/mock-reporting/watches.ts +1 -1
  101. package/src/client/conversations/transcriptStyles.ts +0 -10
  102. package/src/client/pages/automations/AutomationDetails.tsx +0 -193
  103. package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
  104. package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
@@ -1,6 +1,15 @@
1
1
  import type { ConversationUsage } from "@sentry/junior/api/schema";
2
2
 
3
3
  import { formatCostBreakdown, summarizeCost } from "../format";
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableFooter,
10
+ TableHeader,
11
+ TableRow,
12
+ } from "../components/ui/table";
4
13
  import { cn } from "../styles";
5
14
 
6
15
  /** Align recorded token counts with their costs without adding overlapping counters. */
@@ -31,83 +40,53 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
31
40
  );
32
41
  }
33
42
 
43
+ const total = rows.find((row) => row.label === "Total");
34
44
  return (
35
45
  <>
36
- <div className="min-w-0 overflow-x-auto">
37
- <table
38
- aria-label="Model call token and cost breakdown"
39
- className="w-full min-w-72 table-fixed border-collapse text-xs leading-relaxed"
40
- >
41
- <colgroup>
42
- <col />
43
- <col className="w-24" />
44
- <col className="w-24" />
45
- </colgroup>
46
- <thead>
47
- <tr className="border-b border-dashboard-border text-dashboard-text-muted">
48
- <th scope="col" className="py-2 text-left font-normal">
49
- Usage
50
- </th>
51
- <th scope="col" className="py-2 text-right font-normal">
52
- Tokens
53
- </th>
54
- <th scope="col" className="py-2 text-right font-normal">
55
- Cost (USD)
56
- </th>
57
- </tr>
58
- </thead>
59
- <tbody>
60
- {rows.map((row) => (
61
- <tr
62
- key={row.label}
63
- className={cn(
64
- "border-b border-dashboard-border-subtle last:border-b-0",
65
- row.label === "Total" &&
66
- "border-t border-dashboard-border font-semibold",
67
- )}
68
- >
69
- <th
70
- scope="row"
71
- className={cn(
72
- "py-2 text-left font-normal",
73
- row.label === "Total"
74
- ? "font-semibold text-dashboard-text"
75
- : "text-dashboard-text-muted",
76
- )}
77
- >
78
- {row.label}
79
- </th>
80
- <td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
81
- {row.tokens === undefined ? (
82
- <span
83
- aria-label="Not recorded"
84
- className="text-dashboard-text-faint"
85
- >
86
- —
87
- </span>
88
- ) : (
89
- row.tokens.toLocaleString("en-US")
90
- )}
91
- </td>
92
- <td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
93
- {row.cost === undefined ? (
94
- <span
95
- aria-label="Not recorded separately"
96
- className="text-dashboard-text-faint"
97
- >
98
- —
99
- </span>
100
- ) : (
101
- <span title={`${row.cost} USD`}>
102
- {formatCostBreakdown({ total: row.cost })}
103
- </span>
104
- )}
105
- </td>
106
- </tr>
46
+ <Table
47
+ aria-label="Model call token and cost breakdown"
48
+ className="min-w-72 table-fixed text-xs leading-relaxed"
49
+ >
50
+ <colgroup>
51
+ <col />
52
+ <col className="w-24" />
53
+ <col className="w-24" />
54
+ </colgroup>
55
+ <TableHeader>
56
+ <TableRow className="border-dashboard-border">
57
+ <TableHead
58
+ scope="col"
59
+ className="px-0 py-2 leading-relaxed font-normal"
60
+ >
61
+ Usage
62
+ </TableHead>
63
+ <TableHead
64
+ scope="col"
65
+ className="px-0 py-2 text-right leading-relaxed font-normal"
66
+ >
67
+ Tokens
68
+ </TableHead>
69
+ <TableHead
70
+ scope="col"
71
+ className="px-0 py-2 text-right leading-relaxed font-normal"
72
+ >
73
+ Cost (USD)
74
+ </TableHead>
75
+ </TableRow>
76
+ </TableHeader>
77
+ <TableBody>
78
+ {rows
79
+ .filter((row) => row !== total)
80
+ .map((row) => (
81
+ <UsageRow key={row.label} {...row} />
107
82
  ))}
108
- </tbody>
109
- </table>
110
- </div>
83
+ </TableBody>
84
+ {total ? (
85
+ <TableFooter className="font-semibold">
86
+ <UsageRow {...total} total />
87
+ </TableFooter>
88
+ ) : null}
89
+ </Table>
111
90
  <p className="m-0 text-xs leading-relaxed text-dashboard-text-muted">
112
91
  This call only · Estimated costs
113
92
  <br />
@@ -116,3 +95,49 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
116
95
  </>
117
96
  );
118
97
  }
98
+
99
+ function UsageRow(props: {
100
+ cost?: number;
101
+ label: string;
102
+ tokens?: number;
103
+ total?: boolean;
104
+ }) {
105
+ return (
106
+ <TableRow>
107
+ <TableHead
108
+ scope="row"
109
+ className={cn(
110
+ "px-0 py-2 leading-relaxed",
111
+ props.total
112
+ ? "font-semibold text-dashboard-text"
113
+ : "font-normal text-dashboard-text-muted",
114
+ )}
115
+ >
116
+ {props.label}
117
+ </TableHead>
118
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
119
+ {props.tokens === undefined ? (
120
+ <span aria-label="Not recorded" className="text-dashboard-text-faint">
121
+ —
122
+ </span>
123
+ ) : (
124
+ props.tokens.toLocaleString("en-US")
125
+ )}
126
+ </TableCell>
127
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
128
+ {props.cost === undefined ? (
129
+ <span
130
+ aria-label="Not recorded separately"
131
+ className="text-dashboard-text-faint"
132
+ >
133
+ —
134
+ </span>
135
+ ) : (
136
+ <span title={`${props.cost} USD`}>
137
+ {formatCostBreakdown({ total: props.cost })}
138
+ </span>
139
+ )}
140
+ </TableCell>
141
+ </TableRow>
142
+ );
143
+ }
@@ -1,16 +1,22 @@
1
1
  import { GitFork } from "lucide-react";
2
- import { useId, useState } from "react";
3
- import { createPortal } from "react-dom";
2
+ import { useId, useRef, useState } from "react";
4
3
  import { useNavigate } from "react-router";
5
4
  import { Button } from "../components/ui/button";
6
5
  import { Textarea } from "../components/ui/textarea";
7
- import { Field, FieldLabel } from "../components/ui/field";
6
+ import { Field, FieldError, FieldLabel } from "../components/ui/field";
7
+ import {
8
+ Dialog,
9
+ DialogClose,
10
+ DialogContent,
11
+ DialogFooter,
12
+ DialogTitle,
13
+ } from "../components/ui/dialog";
14
+ import { useSheet } from "../components/ui/useSheet";
8
15
  import {
9
16
  Tooltip,
10
17
  TooltipContent,
11
18
  TooltipTrigger,
12
19
  } from "../components/ui/tooltip";
13
- import { Drawer } from "../components/Drawer";
14
20
  import { DashboardApiError } from "../http";
15
21
  import { conversationPath } from "./conversationRoutes";
16
22
  import { useForkConversation } from "./queries";
@@ -36,26 +42,22 @@ export function ForkConversationButton(props: ForkTarget) {
36
42
  />
37
43
  <TooltipContent size="compact">Fork after this message</TooltipContent>
38
44
  </Tooltip>
39
- {open
40
- ? createPortal(
41
- <ForkConversationDialog
42
- {...props}
43
- onClose={() => setOpen(false)}
44
- />,
45
- document.body,
46
- )
47
- : null}
45
+ {open ? (
46
+ <ForkConversationDialog {...props} onClose={() => setOpen(false)} />
47
+ ) : null}
48
48
  </>
49
49
  );
50
50
  }
51
51
 
52
52
  function ForkConversationDialog(props: ForkTarget & { onClose(): void }) {
53
- const titleId = useId();
54
53
  const inputId = useId();
54
+ const errorId = `${inputId}-error`;
55
+ const inputRef = useRef<HTMLTextAreaElement>(null);
55
56
  const [idempotencyKey] = useState(() => crypto.randomUUID());
56
57
  const [message, setMessage] = useState("");
57
58
  const navigate = useNavigate();
58
59
  const fork = useForkConversation(props.conversationId);
60
+ const sheet = useSheet(props.onClose, inputRef);
59
61
  const submit = () => {
60
62
  if (!message.trim() || fork.isPending) return;
61
63
  fork.mutate(
@@ -68,68 +70,72 @@ function ForkConversationDialog(props: ForkTarget & { onClose(): void }) {
68
70
  },
69
71
  );
70
72
  };
73
+ const error = fork.error
74
+ ? (fork.error instanceof DashboardApiError && fork.error.apiError) ||
75
+ "Could not fork the conversation. Try again."
76
+ : undefined;
71
77
  return (
72
- <Drawer
73
- closeLabel="Close fork dialog"
74
- header={
75
- <h2 id={titleId} className="m-0 text-lg font-semibold">
76
- Fork Conversation
77
- </h2>
78
- }
79
- titleId={titleId}
80
- openKey={idempotencyKey}
81
- onClose={() => {
82
- if (!fork.isPending) props.onClose();
78
+ <Dialog
79
+ {...sheet.rootProps}
80
+ onOpenChange={(nextOpen, details) => {
81
+ // A pending fork must finish so its navigation is not lost.
82
+ if (!nextOpen && fork.isPending) {
83
+ details.cancel();
84
+ return;
85
+ }
86
+ sheet.rootProps.onOpenChange(nextOpen, details);
83
87
  }}
84
- width="narrow"
85
88
  >
86
- <form
87
- className="grid gap-4 text-sm"
88
- onSubmit={(event) => {
89
- event.preventDefault();
90
- submit();
91
- }}
92
- >
93
- <p className="m-0">
94
- Creates a fork of the conversation from this point.
95
- </p>
96
- <Field>
97
- <FieldLabel htmlFor={inputId}>Message</FieldLabel>
98
- <Textarea
99
- id={inputId}
100
- autoFocus
101
- className="min-h-28 leading-relaxed"
102
- placeholder="Message Junior…"
103
- value={message}
104
- disabled={fork.isPending}
105
- onChange={(event) => setMessage(event.target.value)}
106
- onKeyDown={(event) => {
107
- // Same keys as the composer: Enter sends, Shift+Enter adds a line.
108
- if (
109
- event.key === "Enter" &&
110
- !event.shiftKey &&
111
- !event.nativeEvent.isComposing
112
- ) {
113
- event.preventDefault();
114
- submit();
115
- }
116
- }}
117
- />
118
- </Field>
119
- {fork.error ? (
120
- <p role="alert" className="m-0 text-red-300">
121
- {(fork.error instanceof DashboardApiError && fork.error.apiError) ||
122
- "Could not fork the conversation. Try again."}
123
- </p>
124
- ) : null}
125
- <Button
126
- variant="primary"
127
- type="submit"
128
- disabled={!message.trim() || fork.isPending}
89
+ <DialogContent {...sheet.contentProps}>
90
+ <form
91
+ className="grid gap-4"
92
+ onSubmit={(event) => {
93
+ event.preventDefault();
94
+ submit();
95
+ }}
129
96
  >
130
- {fork.isPending ? "Forking…" : "Fork and send"}
131
- </Button>
132
- </form>
133
- </Drawer>
97
+ <DialogTitle>Fork Conversation</DialogTitle>
98
+ <Field>
99
+ <FieldLabel htmlFor={inputId}>Message</FieldLabel>
100
+ <Textarea
101
+ ref={inputRef}
102
+ id={inputId}
103
+ aria-describedby={error ? errorId : undefined}
104
+ aria-invalid={error ? true : undefined}
105
+ className="min-h-24 leading-relaxed"
106
+ placeholder="Message Junior…"
107
+ value={message}
108
+ disabled={fork.isPending}
109
+ onChange={(event) => setMessage(event.target.value)}
110
+ onKeyDown={(event) => {
111
+ // Same keys as the composer: Enter sends, Shift+Enter adds a line.
112
+ if (
113
+ event.key === "Enter" &&
114
+ !event.shiftKey &&
115
+ !event.nativeEvent.isComposing
116
+ ) {
117
+ event.preventDefault();
118
+ submit();
119
+ }
120
+ }}
121
+ />
122
+ <FieldError id={errorId}>{error}</FieldError>
123
+ </Field>
124
+ <DialogFooter>
125
+ <DialogClose
126
+ disabled={fork.isPending}
127
+ render={<Button>Cancel</Button>}
128
+ />
129
+ <Button
130
+ variant="primary"
131
+ type="submit"
132
+ disabled={!message.trim() || fork.isPending}
133
+ >
134
+ {fork.isPending ? "Forking…" : "Fork and send"}
135
+ </Button>
136
+ </DialogFooter>
137
+ </form>
138
+ </DialogContent>
139
+ </Dialog>
134
140
  );
135
141
  }