@agent-native/toolkit 0.198.2 → 0.198.3-nightly-20260930154911

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 (117) hide show
  1. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  2. package/dist/app/agentkit/react/components.js +13 -5
  3. package/dist/app/agentkit/react/components.js.map +1 -1
  4. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  5. package/dist/app/chat/AgentKitAssistantChat.js +20 -6
  6. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  7. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  8. package/dist/app/i18n/catalogs/ar-SA.js +211 -0
  9. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  10. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  11. package/dist/app/i18n/catalogs/de-DE.js +147 -0
  12. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  13. package/dist/app/i18n/catalogs/en-US.d.ts +147 -0
  14. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  15. package/dist/app/i18n/catalogs/en-US.js +147 -0
  16. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  17. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  18. package/dist/app/i18n/catalogs/es-ES.js +163 -0
  19. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  20. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  21. package/dist/app/i18n/catalogs/fr-FR.js +163 -0
  22. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  23. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  24. package/dist/app/i18n/catalogs/hi-IN.js +147 -0
  25. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  26. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  27. package/dist/app/i18n/catalogs/ja-JP.js +131 -0
  28. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  29. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  30. package/dist/app/i18n/catalogs/ko-KR.js +131 -0
  31. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  32. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/pt-BR.js +163 -0
  34. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  35. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/zh-CN.js +131 -0
  37. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  38. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  39. package/dist/app/i18n/catalogs/zh-TW.js +131 -0
  40. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  41. package/dist/app/observability/ObservabilityDashboard.d.ts.map +1 -1
  42. package/dist/app/observability/ObservabilityDashboard.js +101 -109
  43. package/dist/app/observability/ObservabilityDashboard.js.map +1 -1
  44. package/dist/app/observability/RunInsights.d.ts +22 -0
  45. package/dist/app/observability/RunInsights.d.ts.map +1 -0
  46. package/dist/app/observability/RunInsights.js +658 -0
  47. package/dist/app/observability/RunInsights.js.map +1 -0
  48. package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
  49. package/dist/composer/RealtimeVoiceMode.js +1 -1
  50. package/dist/composer/RealtimeVoiceMode.js.map +1 -1
  51. package/package.json +5 -5
  52. package/src/app/agent-page/ConnectionsTab.spec.tsx +0 -3
  53. package/src/app/agentkit/react/components.spec.tsx +2 -1
  54. package/src/app/agentkit/react/components.tsx +14 -5
  55. package/src/app/agentkit/react/composer-context.spec.tsx +4 -1
  56. package/src/app/agentkit/react/suggestions.spec.tsx +51 -1
  57. package/src/app/chat/AgentKitAssistantChat.spec.tsx +57 -0
  58. package/src/app/chat/AgentKitAssistantChat.tsx +44 -8
  59. package/src/app/chat/AgentPanel.header.spec.ts +22 -22
  60. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +17 -2
  61. package/src/app/chat/agentkit-chat/approval-card.spec.tsx +2 -2
  62. package/src/app/chat/agentkit-chat/connections.spec.tsx +1 -1
  63. package/src/app/chat/chat/markdown-renderer.spec.tsx +1 -1
  64. package/src/app/chat/chat/tool-call-display.spec.tsx +25 -2
  65. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +38 -26
  66. package/src/app/chat/chat/widgets/agent-team-progress-widget.spec.tsx +7 -1
  67. package/src/app/chat/chat/widgets/builtin-connect-required-renderer.spec.tsx +24 -1
  68. package/src/app/chat/chat/widgets/record-change-widget.spec.tsx +16 -1
  69. package/src/app/chat/thinking-display.spec.tsx +1 -1
  70. package/src/app/db-admin/useAgentSync.spec.tsx +1 -1
  71. package/src/app/extensions/ExtensionViewer.spec.tsx +2 -1
  72. package/src/app/i18n/catalogs/ar-SA.ts +282 -0
  73. package/src/app/i18n/catalogs/de-DE.ts +198 -0
  74. package/src/app/i18n/catalogs/en-US.ts +187 -0
  75. package/src/app/i18n/catalogs/es-ES.ts +221 -0
  76. package/src/app/i18n/catalogs/fr-FR.ts +219 -0
  77. package/src/app/i18n/catalogs/hi-IN.ts +182 -0
  78. package/src/app/i18n/catalogs/ja-JP.ts +160 -0
  79. package/src/app/i18n/catalogs/ko-KR.ts +160 -0
  80. package/src/app/i18n/catalogs/pt-BR.ts +220 -0
  81. package/src/app/i18n/catalogs/zh-CN.ts +154 -0
  82. package/src/app/i18n/catalogs/zh-TW.ts +154 -0
  83. package/src/app/integrations/BuilderIntegrationPage.spec.tsx +3 -3
  84. package/src/app/integrations/ChannelsPage.spec.tsx +3 -3
  85. package/src/app/integrations/IntegrationsPage.spec.tsx +3 -3
  86. package/src/app/integrations/IntegrationsPanel.spec.tsx +7 -4
  87. package/src/app/observability/ObservabilityDashboard.review.spec.tsx +211 -52
  88. package/src/app/observability/ObservabilityDashboard.spec.tsx +80 -20
  89. package/src/app/observability/ObservabilityDashboard.tsx +240 -400
  90. package/src/app/observability/RunInsights.tsx +1603 -0
  91. package/src/app/observability/ThumbsFeedback.spec.tsx +29 -12
  92. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +25 -5
  93. package/src/app/onboarding/first-run-startup-gate.spec.tsx +12 -7
  94. package/src/app/resources/McpConnectionSuggestion.render.spec.tsx +43 -5
  95. package/src/app/resources/McpIntegrationDialog.spec.tsx +20 -14
  96. package/src/app/resources/ResourcesPanel.spec.ts +9 -7
  97. package/src/app/review/ReviewThreadPanel.spec.tsx +2 -2
  98. package/src/app/settings/SecretsSection.spec.tsx +12 -3
  99. package/src/app/settings/SettingsTabsPage.spec.tsx +4 -1
  100. package/src/app/settings/VoiceTranscriptionSection.spec.tsx +12 -1
  101. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -1
  102. package/src/app/settings/account/ProfileSettings.spec.tsx +13 -1
  103. package/src/app/settings/account/SecuritySettings.spec.tsx +12 -1
  104. package/src/app/settings/shell/SettingsShell.spec.tsx +35 -15
  105. package/src/app/settings/shell/pages/app-group-pages.spec.tsx +7 -0
  106. package/src/app/settings/shell/pages/audit.spec.tsx +5 -2
  107. package/src/app/settings/shell/pages/resource-pages.spec.tsx +25 -16
  108. package/src/app/settings/shell/pages/sub-agents.spec.tsx +24 -15
  109. package/src/app/settings/usage/UsagePage.spec.tsx +4 -0
  110. package/src/app/setup-connections/BuilderConnectCard.spec.tsx +27 -8
  111. package/src/app/sharing/ShareButton.spec.tsx +7 -3
  112. package/src/app/sharing/ShareDialog.spec.tsx +4 -4
  113. package/src/app/sharing/useShareButtonController.spec.tsx +1 -1
  114. package/src/app/sharing/useShareDialogController.spec.tsx +2 -2
  115. package/src/app/transcription/BuilderTranscriptionCta.spec.tsx +1 -1
  116. package/src/composer/RealtimeVoiceMode.spec.tsx +1 -0
  117. package/src/composer/RealtimeVoiceMode.tsx +1 -5
@@ -1,5 +1,4 @@
1
1
  import {
2
- requestAgentChatThreadOpen,
3
2
  sendToAgentChat,
4
3
  sendToAgentChatAndConfirm,
5
4
  } from "@agent-native/core/client/agent-chat";
@@ -49,8 +48,6 @@ import {
49
48
  IconMessages,
50
49
  IconThumbUp,
51
50
  IconThumbDown,
52
- IconClock,
53
- IconCoin,
54
51
  IconTool,
55
52
  IconMoodSmile,
56
53
  IconChartBar,
@@ -78,9 +75,13 @@ import {
78
75
  type ObservabilityReviewSummaryStatus,
79
76
  } from "./ObservabilityReviewSummaryButton.js";
80
77
  import { OutputPreview, parseOutputPreview } from "./OutputPreview.js";
78
+ import {
79
+ RunInsightsConversations,
80
+ RunInsightsOverview,
81
+ type RunFilter,
82
+ } from "./RunInsights.js";
81
83
  import {
82
84
  useObservabilityOverview,
83
- useTraces,
84
85
  useTraceDetail,
85
86
  usePromoteTraceEval,
86
87
  useFeedbackList,
@@ -93,23 +94,9 @@ import {
93
94
  useOutputReviewDetail,
94
95
  useSaveInstructionUpdate,
95
96
  useSaveReviewFeedback,
96
- type TraceSummary,
97
97
  type Experiment,
98
98
  } from "./useObservability.js";
99
99
 
100
- function formatCost(centsX100: number): string {
101
- const cents = centsX100 / 100;
102
- if (cents < 1) return `${cents.toFixed(3)}¢`;
103
- if (cents < 100) return `${cents.toFixed(2)}¢`;
104
- return `$${(cents / 100).toFixed(2)}`;
105
- }
106
-
107
- function formatCostCents(cents: number): string {
108
- if (cents < 1) return `${cents.toFixed(3)}¢`;
109
- if (cents < 100) return `${cents.toFixed(2)}¢`;
110
- return `$${(cents / 100).toFixed(2)}`;
111
- }
112
-
113
100
  function formatDuration(ms: number): string {
114
101
  if (ms < 1000) return `${Math.round(ms)}ms`;
115
102
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
@@ -454,144 +441,58 @@ function LoadingState() {
454
441
  );
455
442
  }
456
443
 
457
- function OverviewTab({ days }: { days: number }) {
444
+ function OverviewTab({
445
+ days,
446
+ onShowRuns,
447
+ }: {
448
+ days: number;
449
+ onShowRuns: (filter: RunFilter) => void;
450
+ }) {
458
451
  const t = useT();
459
- const { data, isLoading } = useObservabilityOverview(days);
460
-
461
- if (isLoading) return <LoadingState />;
462
- if (!data) return <EmptyState message={t("observability.noData")} />;
463
-
452
+ const { data } = useObservabilityOverview(days);
464
453
  return (
465
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
466
- <MetricCard
467
- label={t("observability.totalRuns")}
468
- value={String(data.totalRuns)}
469
- icon={<IconActivity size={16} />}
470
- />
471
- <MetricCard
472
- label={t("observability.totalCost")}
473
- value={formatCostCents(data.totalCostCents)}
474
- icon={<IconCoin size={16} />}
475
- />
476
- <MetricCard
477
- label={t("observability.avgLatency")}
478
- value={formatDuration(data.avgDurationMs)}
479
- icon={<IconClock size={16} />}
480
- />
481
- <MetricCard
482
- label={t("observability.toolSuccess")}
483
- value={formatPercent(data.toolSuccessRate)}
484
- icon={<IconTool size={16} />}
485
- />
486
- <MetricCard
487
- label={t("observability.thumbsUp")}
488
- value={formatPercent(data.thumbsUpRate)}
489
- icon={<IconThumbUp size={16} />}
490
- />
491
- <MetricCard
492
- label={t("observability.avgEvalScore")}
493
- value={data.avgEvalScore.toFixed(2)}
494
- icon={<IconMoodSmile size={16} />}
495
- />
496
- </div>
454
+ <RunInsightsOverview
455
+ days={days}
456
+ onShowRuns={onShowRuns}
457
+ extraStats={
458
+ data
459
+ ? [
460
+ {
461
+ label: t("observability.thumbsUp"),
462
+ value: formatPercent(data.thumbsUpRate),
463
+ },
464
+ {
465
+ label: t("observability.avgEvalScore"),
466
+ value: data.avgEvalScore.toFixed(2),
467
+ },
468
+ ]
469
+ : []
470
+ }
471
+ />
497
472
  );
498
473
  }
499
474
 
500
- function ConversationsTab({ days }: { days: number }) {
501
- const t = useT();
502
- const { data: traces, isLoading } = useTraces(days);
503
- const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
504
-
505
- if (selectedRunId) {
506
- return (
507
- <TraceDetailView
508
- key={selectedRunId}
509
- runId={selectedRunId}
510
- onBack={() => setSelectedRunId(null)}
511
- />
512
- );
513
- }
514
-
515
- if (isLoading) return <LoadingState />;
516
- if (!traces || traces.length === 0)
517
- return <EmptyState message={t("observability.noConversations")} />;
518
-
475
+ function ConversationsTab({
476
+ days,
477
+ filter,
478
+ onClearFilter,
479
+ }: {
480
+ days: number;
481
+ filter: RunFilter | null;
482
+ onClearFilter: () => void;
483
+ }) {
519
484
  return (
520
- <div className="overflow-hidden rounded-xl border border-border/70 bg-card">
521
- <table className="w-full table-fixed text-left text-xs">
522
- <thead>
523
- <tr className="border-b border-border bg-muted/30">
524
- <th className="px-3 py-2 font-medium text-muted-foreground w-[15%]">
525
- {t("observability.run")}
526
- </th>
527
- <th className="px-3 py-2 font-medium text-muted-foreground w-[20%]">
528
- {t("observability.model")}
529
- </th>
530
- <th className="px-3 py-2 font-medium text-muted-foreground">
531
- {t("observability.duration")}
532
- </th>
533
- <th className="px-3 py-2 font-medium text-muted-foreground">
534
- {t("observability.cost")}
535
- </th>
536
- <th className="px-3 py-2 font-medium text-muted-foreground">
537
- {t("observability.tools")}
538
- </th>
539
- <th className="px-3 py-2 font-medium text-muted-foreground">
540
- {t("observability.time")}
541
- </th>
542
- <th className="w-8" />
543
- </tr>
544
- </thead>
545
- <tbody>
546
- {traces.map((trace: TraceSummary) => (
547
- <tr
548
- key={trace.runId}
549
- onClick={() => setSelectedRunId(trace.runId)}
550
- className="border-b border-border last:border-b-0 cursor-pointer hover:bg-accent/30"
551
- >
552
- <td className="px-3 py-2 font-mono text-foreground truncate">
553
- {truncateId(trace.runId)}
554
- </td>
555
- <td className="px-3 py-2 text-muted-foreground truncate">
556
- {trace.model || "unknown"}
557
- </td>
558
- <td className="px-3 py-2 tabular-nums text-muted-foreground">
559
- {formatDuration(trace.totalDurationMs)}
560
- </td>
561
- <td className="px-3 py-2 tabular-nums text-muted-foreground">
562
- {formatCost(trace.totalCostCentsX100)}
563
- </td>
564
- <td className="px-3 py-2 tabular-nums text-muted-foreground">
565
- {trace.toolCalls}
566
- {trace.failedTools > 0 && (
567
- <span className="ml-1 text-red-500">
568
- {t("observability.failedCount", {
569
- count: trace.failedTools,
570
- })}
571
- </span>
572
- )}
573
- </td>
574
- <td className="px-3 py-2 text-muted-foreground truncate">
575
- {timeAgo(trace.createdAt)}
576
- </td>
577
- <td className="px-3 py-2">
578
- <IconChevronRight size={14} className="text-muted-foreground" />
579
- </td>
580
- </tr>
581
- ))}
582
- </tbody>
583
- </table>
584
- </div>
485
+ <RunInsightsConversations
486
+ days={days}
487
+ filter={filter}
488
+ onClearFilter={onClearFilter}
489
+ renderRawTrace={(runId) => <RawTrace runId={runId} />}
490
+ />
585
491
  );
586
492
  }
587
493
 
588
- function TraceDetailView({
589
- runId,
590
- onBack,
591
- }: {
592
- runId: string;
593
- onBack: () => void;
594
- }) {
494
+ /** Every recorded span for a run, with inputs, outputs and metadata when captured. */
495
+ function RawTrace({ runId }: { runId: string }) {
595
496
  const t = useT();
596
497
  const { data, isLoading } = useTraceDetail(runId);
597
498
  const [expandedSpanId, setExpandedSpanId] = useState<string | null>(null);
@@ -602,280 +503,209 @@ function TraceDetailView({
602
503
  const canPromote =
603
504
  !!data && !promote.isPending && (!needsNeedle || needle.length > 0);
604
505
 
506
+ if (isLoading) return <LoadingState />;
507
+ if (!data) return null;
605
508
  return (
606
- <div>
607
- <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
509
+ <div className="space-y-3">
510
+ <div className="flex flex-wrap items-center justify-end gap-2">
511
+ <input
512
+ type="text"
513
+ value={mustContain}
514
+ onChange={(event) => setMustContain(event.target.value)}
515
+ disabled={promote.isPending}
516
+ placeholder={t(
517
+ needsNeedle
518
+ ? "observability.promoteMustContain"
519
+ : "observability.promoteMustContainOptional",
520
+ )}
521
+ aria-label={t("observability.promoteMustContainLabel", {
522
+ defaultValue: "Text the promoted eval reply must contain",
523
+ })}
524
+ className="w-56 rounded-md border border-border bg-background px-2.5 py-1 text-xs text-foreground placeholder:text-muted-foreground disabled:opacity-50"
525
+ />
608
526
  <button
609
- onClick={onBack}
610
- className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
527
+ type="button"
528
+ disabled={!canPromote}
529
+ onClick={() =>
530
+ promote.mutate({
531
+ runId,
532
+ ...(needle ? { mustContain: needle } : {}),
533
+ })
534
+ }
535
+ className="rounded-md border border-border bg-background px-2.5 py-1 text-xs font-medium text-foreground hover:bg-accent disabled:opacity-50"
611
536
  >
612
- <IconArrowLeft size={14} />
613
- {t("observability.backToList")}
537
+ {promote.isPending
538
+ ? t("observability.promotingToEval")
539
+ : t("observability.promoteToEval")}
614
540
  </button>
615
- <div className="flex flex-wrap items-center justify-end gap-2">
616
- <input
617
- type="text"
618
- value={mustContain}
619
- onChange={(event) => setMustContain(event.target.value)}
620
- disabled={promote.isPending || !data}
621
- placeholder={t(
622
- needsNeedle
623
- ? "observability.promoteMustContain"
624
- : "observability.promoteMustContainOptional",
625
- )}
626
- aria-label={t("observability.promoteMustContainLabel", {
627
- defaultValue: "Text the promoted eval reply must contain",
628
- })}
629
- className="w-56 rounded-md border border-border bg-background px-2.5 py-1 text-xs text-foreground placeholder:text-muted-foreground disabled:opacity-50"
630
- />
631
- <button
632
- type="button"
633
- disabled={!canPromote}
634
- onClick={() =>
635
- promote.mutate({
636
- runId,
637
- ...(needle ? { mustContain: needle } : {}),
638
- })
639
- }
640
- className="rounded-md border border-border bg-background px-2.5 py-1 text-xs font-medium text-foreground hover:bg-accent disabled:opacity-50"
641
- >
642
- {promote.isPending
643
- ? t("observability.promotingToEval")
644
- : t("observability.promoteToEval")}
645
- </button>
646
- </div>
647
541
  </div>
648
542
  {needsNeedle && needle.length === 0 && (
649
- <p className="mb-3 text-xs text-muted-foreground">
543
+ <p className="text-xs text-muted-foreground">
650
544
  {t("observability.promoteNeedsContains")}
651
545
  </p>
652
546
  )}
653
-
654
547
  {promote.isSuccess && promote.data && (
655
- <p className="mb-3 text-xs text-muted-foreground">
548
+ <p className="text-xs text-muted-foreground">
656
549
  {t("observability.promotedEval", { id: promote.data.dataset.id })}{" "}
657
550
  {t("observability.promotedEvalHint", { runId })}
658
551
  </p>
659
552
  )}
660
553
  {promote.isError && (
661
- <p className="mb-3 text-xs text-destructive">
554
+ <p className="text-xs text-destructive">
662
555
  {promote.error instanceof Error
663
556
  ? promote.error.message
664
557
  : t("observability.promoteEvalFailed")}
665
558
  </p>
666
559
  )}
667
-
668
- {isLoading && <LoadingState />}
669
-
670
- {data && (
671
- <div className="space-y-4">
672
- <div className="flex items-center justify-end">
673
- {data.summary.threadId && (
674
- <button
675
- type="button"
676
- onClick={() =>
677
- requestAgentChatThreadOpen({
678
- threadId: data.summary.threadId!,
679
- })
680
- }
681
- className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground"
682
- >
683
- <IconMessages size={14} />
684
- {t("observability.openFullConversation")}
685
- </button>
686
- )}
687
- </div>
688
-
689
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
690
- <div className="rounded-xl border border-border/70 bg-card p-3 text-card-foreground">
691
- <div className="text-[10px] text-muted-foreground mb-1">
692
- {t("observability.model")}
693
- </div>
694
- <div className="text-sm font-medium text-foreground truncate">
695
- {data.summary.model || "unknown"}
696
- </div>
697
- </div>
698
- <div className="rounded-xl border border-border/70 bg-card p-3 text-card-foreground">
699
- <div className="text-[10px] text-muted-foreground mb-1">
560
+ <div className="overflow-hidden rounded-xl border border-border/70 bg-card">
561
+ <table className="w-full table-fixed text-left text-xs">
562
+ <thead>
563
+ <tr className="border-b border-border bg-muted/30">
564
+ <th className="px-3 py-2 font-medium text-muted-foreground w-[15%]">
565
+ {t("observability.type")}
566
+ </th>
567
+ <th className="px-3 py-2 font-medium text-muted-foreground w-[35%]">
568
+ {t("observability.name")}
569
+ </th>
570
+ <th className="px-3 py-2 font-medium text-muted-foreground">
700
571
  {t("observability.duration")}
701
- </div>
702
- <div className="text-sm font-medium tabular-nums text-foreground">
703
- {formatDuration(data.summary.totalDurationMs)}
704
- </div>
705
- </div>
706
- <div className="rounded-xl border border-border/70 bg-card p-3 text-card-foreground">
707
- <div className="text-[10px] text-muted-foreground mb-1">
708
- {t("observability.cost")}
709
- </div>
710
- <div className="text-sm font-medium tabular-nums text-foreground">
711
- {formatCost(data.summary.totalCostCentsX100)}
712
- </div>
713
- </div>
714
- <div className="rounded-xl border border-border/70 bg-card p-3 text-card-foreground">
715
- <div className="text-[10px] text-muted-foreground mb-1">
716
- {t("observability.spans")}
717
- </div>
718
- <div className="text-sm font-medium tabular-nums text-foreground">
719
- {data.summary.totalSpans}
720
- </div>
721
- </div>
722
- </div>
572
+ </th>
573
+ <th className="px-3 py-2 font-medium text-muted-foreground">
574
+ {t("observability.tokens")}
575
+ </th>
576
+ <th className="px-3 py-2 font-medium text-muted-foreground">
577
+ {t("observability.status")}
578
+ </th>
579
+ <th className="w-10" />
580
+ </tr>
581
+ </thead>
582
+ <tbody>
583
+ {data.spans.map((span) => {
584
+ const expanded = expandedSpanId === span.id;
585
+ const metadata = span.metadata ?? {};
586
+ const fields: Array<{ label: string; value: unknown }> = [];
587
+ if (Object.hasOwn(metadata, "input")) {
588
+ fields.push({
589
+ label: t("observability.input"),
590
+ value: metadata.input,
591
+ });
592
+ } else if (span.spanType === "tool_call") {
593
+ fields.push({
594
+ label: t("observability.input"),
595
+ value: t("observability.notCaptured"),
596
+ });
597
+ }
598
+ if (Object.hasOwn(metadata, "output")) {
599
+ fields.push({
600
+ label: t("observability.output"),
601
+ value: metadata.output,
602
+ });
603
+ } else if (span.spanType === "tool_call") {
604
+ fields.push({
605
+ label: t("observability.output"),
606
+ value: t("observability.notCaptured"),
607
+ });
608
+ }
609
+ if (span.errorMessage || span.status === "error") {
610
+ fields.push({
611
+ label: t("observability.error"),
612
+ value: span.errorMessage ?? t("observability.notCaptured"),
613
+ });
614
+ }
615
+ const otherMetadata = Object.fromEntries(
616
+ Object.entries(metadata).filter(
617
+ ([key]) => key !== "input" && key !== "output",
618
+ ),
619
+ );
620
+ if (Object.keys(otherMetadata).length > 0) {
621
+ fields.push({
622
+ label: t("observability.metadata"),
623
+ value: otherMetadata,
624
+ });
625
+ }
626
+ if (fields.length === 0) {
627
+ fields.push({
628
+ label: t("observability.metadata"),
629
+ value: t("observability.notCaptured"),
630
+ });
631
+ }
723
632
 
724
- <div className="overflow-hidden rounded-xl border border-border/70 bg-card">
725
- <table className="w-full table-fixed text-left text-xs">
726
- <thead>
727
- <tr className="border-b border-border bg-muted/30">
728
- <th className="px-3 py-2 font-medium text-muted-foreground w-[15%]">
729
- {t("observability.type")}
730
- </th>
731
- <th className="px-3 py-2 font-medium text-muted-foreground w-[35%]">
732
- {t("observability.name")}
733
- </th>
734
- <th className="px-3 py-2 font-medium text-muted-foreground">
735
- {t("observability.duration")}
736
- </th>
737
- <th className="px-3 py-2 font-medium text-muted-foreground">
738
- {t("observability.tokens")}
739
- </th>
740
- <th className="px-3 py-2 font-medium text-muted-foreground">
741
- {t("observability.status")}
742
- </th>
743
- <th className="w-10" />
744
- </tr>
745
- </thead>
746
- <tbody>
747
- {data.spans.map((span) => {
748
- const expanded = expandedSpanId === span.id;
749
- const metadata = span.metadata ?? {};
750
- const fields: Array<{ label: string; value: unknown }> = [];
751
- if (Object.hasOwn(metadata, "input")) {
752
- fields.push({
753
- label: t("observability.input"),
754
- value: metadata.input,
755
- });
756
- } else if (span.spanType === "tool_call") {
757
- fields.push({
758
- label: t("observability.input"),
759
- value: t("observability.notCaptured"),
760
- });
761
- }
762
- if (Object.hasOwn(metadata, "output")) {
763
- fields.push({
764
- label: t("observability.output"),
765
- value: metadata.output,
766
- });
767
- } else if (span.spanType === "tool_call") {
768
- fields.push({
769
- label: t("observability.output"),
770
- value: t("observability.notCaptured"),
771
- });
772
- }
773
- if (span.errorMessage || span.status === "error") {
774
- fields.push({
775
- label: t("observability.error"),
776
- value:
777
- span.errorMessage ?? t("observability.notCaptured"),
778
- });
779
- }
780
- const otherMetadata = Object.fromEntries(
781
- Object.entries(metadata).filter(
782
- ([key]) => key !== "input" && key !== "output",
783
- ),
784
- );
785
- if (Object.keys(otherMetadata).length > 0) {
786
- fields.push({
787
- label: t("observability.metadata"),
788
- value: otherMetadata,
789
- });
790
- }
791
- if (fields.length === 0) {
792
- fields.push({
793
- label: t("observability.metadata"),
794
- value: t("observability.notCaptured"),
795
- });
796
- }
797
-
798
- return (
799
- <Fragment key={span.id}>
800
- <tr className="border-b border-border last:border-b-0">
801
- <td className="px-3 py-2 truncate">
802
- <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
803
- {span.spanType.replace("_", " ")}
804
- </span>
805
- </td>
806
- <td className="px-3 py-2 font-medium text-foreground truncate">
807
- {span.name}
808
- </td>
809
- <td className="px-3 py-2 tabular-nums text-muted-foreground">
810
- {formatDuration(span.durationMs)}
811
- </td>
812
- <td className="px-3 py-2 tabular-nums text-muted-foreground">
813
- {span.inputTokens + span.outputTokens > 0
814
- ? `${span.inputTokens} / ${span.outputTokens}`
815
- : "-"}
816
- </td>
817
- <td className="px-3 py-2">
818
- <StatusBadge status={span.status} />
819
- </td>
820
- <td className="px-2 py-2 text-right">
821
- <button
822
- type="button"
823
- aria-label={t(
824
- expanded
825
- ? "observability.hideDetails"
826
- : "observability.viewDetails",
827
- )}
828
- aria-expanded={expanded}
829
- aria-controls={
830
- expanded ? `span-details-${span.id}` : undefined
831
- }
832
- onClick={() =>
833
- setExpandedSpanId(expanded ? null : span.id)
834
- }
835
- className="rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
836
- >
837
- <IconChevronRight
838
- size={14}
839
- className={cn(
840
- "transition-transform",
841
- expanded && "rotate-90",
842
- )}
843
- />
844
- </button>
845
- </td>
846
- </tr>
847
- {expanded && (
848
- <tr id={`span-details-${span.id}`}>
849
- <td
850
- colSpan={6}
851
- className="border-b border-border p-3"
852
- >
853
- <div className="grid gap-3 sm:grid-cols-2">
854
- {fields.map(({ label, value }) => (
855
- <div key={label} className="min-w-0">
856
- <div className="mb-1 text-[10px] font-medium text-muted-foreground">
857
- {label}
858
- </div>
859
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md bg-muted/40 p-2 font-mono text-xs text-foreground">
860
- {typeof value === "string"
861
- ? value || '""'
862
- : (JSON.stringify(value, null, 2) ??
863
- String(value))}
864
- </pre>
865
- </div>
866
- ))}
633
+ return (
634
+ <Fragment key={span.id}>
635
+ <tr className="border-b border-border last:border-b-0">
636
+ <td className="px-3 py-2 truncate">
637
+ <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
638
+ {span.spanType.replace("_", " ")}
639
+ </span>
640
+ </td>
641
+ <td className="px-3 py-2 font-medium text-foreground truncate">
642
+ {span.name}
643
+ </td>
644
+ <td className="px-3 py-2 tabular-nums text-muted-foreground">
645
+ {formatDuration(span.durationMs)}
646
+ </td>
647
+ <td className="px-3 py-2 tabular-nums text-muted-foreground">
648
+ {span.inputTokens + span.outputTokens > 0
649
+ ? `${span.inputTokens} / ${span.outputTokens}`
650
+ : "-"}
651
+ </td>
652
+ <td className="px-3 py-2">
653
+ <StatusBadge status={span.status} />
654
+ </td>
655
+ <td className="px-2 py-2 text-right">
656
+ <button
657
+ type="button"
658
+ aria-label={t(
659
+ expanded
660
+ ? "observability.hideDetails"
661
+ : "observability.viewDetails",
662
+ )}
663
+ aria-expanded={expanded}
664
+ aria-controls={
665
+ expanded ? `span-details-${span.id}` : undefined
666
+ }
667
+ onClick={() =>
668
+ setExpandedSpanId(expanded ? null : span.id)
669
+ }
670
+ className="rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
671
+ >
672
+ <IconChevronRight
673
+ size={14}
674
+ className={cn(
675
+ "transition-transform",
676
+ expanded && "rotate-90",
677
+ )}
678
+ />
679
+ </button>
680
+ </td>
681
+ </tr>
682
+ {expanded && (
683
+ <tr id={`span-details-${span.id}`}>
684
+ <td colSpan={6} className="border-b border-border p-3">
685
+ <div className="grid gap-3 sm:grid-cols-2">
686
+ {fields.map(({ label, value }) => (
687
+ <div key={label} className="min-w-0">
688
+ <div className="mb-1 text-[10px] font-medium text-muted-foreground">
689
+ {label}
690
+ </div>
691
+ <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md bg-muted/40 p-2 font-mono text-xs text-foreground">
692
+ {typeof value === "string"
693
+ ? value || '""'
694
+ : (JSON.stringify(value, null, 2) ??
695
+ String(value))}
696
+ </pre>
867
697
  </div>
868
- </td>
869
- </tr>
870
- )}
871
- </Fragment>
872
- );
873
- })}
874
- </tbody>
875
- </table>
876
- </div>
877
- </div>
878
- )}
698
+ ))}
699
+ </div>
700
+ </td>
701
+ </tr>
702
+ )}
703
+ </Fragment>
704
+ );
705
+ })}
706
+ </tbody>
707
+ </table>
708
+ </div>
879
709
  </div>
880
710
  );
881
711
  }
@@ -3219,6 +3049,12 @@ function ObservabilityDashboardContent({
3219
3049
  const t = useT();
3220
3050
  const [localTab, setLocalTab] = useState<TabId>("overview");
3221
3051
  const [days, setDays] = useState(7);
3052
+ const [runFilter, setRunFilter] = useState<RunFilter | null>(null);
3053
+ const showRuns = (filter: RunFilter) => {
3054
+ setRunFilter(filter);
3055
+ if (routeBasePath && navigate) navigate(`${routeBasePath}/conversations`);
3056
+ else setLocalTab("conversations");
3057
+ };
3222
3058
  const visibleTabs = showHumanReview
3223
3059
  ? TABS
3224
3060
  : TABS.filter((tab) => tab.id !== "review");
@@ -3303,7 +3139,7 @@ function ObservabilityDashboardContent({
3303
3139
  </div>
3304
3140
 
3305
3141
  <TabsContent value="overview" className="mt-0">
3306
- <OverviewTab days={days} />
3142
+ <OverviewTab days={days} onShowRuns={showRuns} />
3307
3143
  </TabsContent>
3308
3144
  {showHumanReview && (
3309
3145
  <TabsContent value="review" className="mt-0">
@@ -3314,7 +3150,11 @@ function ObservabilityDashboardContent({
3314
3150
  </TabsContent>
3315
3151
  )}
3316
3152
  <TabsContent value="conversations" className="mt-0">
3317
- <ConversationsTab days={days} />
3153
+ <ConversationsTab
3154
+ days={days}
3155
+ filter={runFilter}
3156
+ onClearFilter={() => setRunFilter(null)}
3157
+ />
3318
3158
  </TabsContent>
3319
3159
  <TabsContent value="evals" className="mt-0">
3320
3160
  <EvalsTab days={days} />