UsageDetailModal.tsx

  1import React from "react";
  2import { Usage } from "../types";
  3
  4interface UsageDetailModalProps {
  5  usage: Usage;
  6  durationMs: number | null;
  7  onClose: () => void;
  8}
  9
 10function UsageDetailModal({ usage, durationMs, onClose }: UsageDetailModalProps) {
 11  // Format duration in human-readable format
 12  const formatDuration = (ms: number): string => {
 13    if (ms < 1000) return `${ms}ms`;
 14    if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
 15    return `${(ms / 60000).toFixed(2)}m`;
 16  };
 17
 18  // Format timestamp for display
 19  const formatTimestamp = (isoString: string): string => {
 20    const date = new Date(isoString);
 21    return date.toLocaleString(undefined, {
 22      year: "numeric",
 23      month: "short",
 24      day: "numeric",
 25      hour: "2-digit",
 26      minute: "2-digit",
 27      second: "2-digit",
 28    });
 29  };
 30
 31  // Close on escape key
 32  React.useEffect(() => {
 33    const handleEscape = (e: KeyboardEvent) => {
 34      if (e.key === "Escape") {
 35        onClose();
 36      }
 37    };
 38    document.addEventListener("keydown", handleEscape);
 39    return () => document.removeEventListener("keydown", handleEscape);
 40  }, [onClose]);
 41
 42  return (
 43    <div
 44      style={{
 45        position: "fixed",
 46        top: 0,
 47        left: 0,
 48        right: 0,
 49        bottom: 0,
 50        backgroundColor: "rgba(0, 0, 0, 0.5)",
 51        display: "flex",
 52        alignItems: "center",
 53        justifyContent: "center",
 54        zIndex: 10001,
 55        padding: "16px",
 56      }}
 57      onClick={onClose}
 58    >
 59      <div
 60        style={{
 61          backgroundColor: "#ffffff",
 62          borderRadius: "8px",
 63          padding: "24px",
 64          maxWidth: "500px",
 65          width: "100%",
 66          boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
 67        }}
 68        onClick={(e) => e.stopPropagation()}
 69      >
 70        <div
 71          style={{
 72            display: "flex",
 73            justifyContent: "space-between",
 74            alignItems: "center",
 75            marginBottom: "20px",
 76          }}
 77        >
 78          <h2 style={{ fontSize: "18px", fontWeight: "600", color: "#1f2937", margin: 0 }}>
 79            Usage Details
 80          </h2>
 81          <button
 82            onClick={onClose}
 83            style={{
 84              background: "none",
 85              border: "none",
 86              fontSize: "24px",
 87              color: "#6b7280",
 88              cursor: "pointer",
 89              padding: "0",
 90              width: "32px",
 91              height: "32px",
 92              display: "flex",
 93              alignItems: "center",
 94              justifyContent: "center",
 95              borderRadius: "4px",
 96            }}
 97            onMouseEnter={(e) => {
 98              e.currentTarget.style.backgroundColor = "#f3f4f6";
 99            }}
100            onMouseLeave={(e) => {
101              e.currentTarget.style.backgroundColor = "transparent";
102            }}
103            aria-label="Close"
104          >
105            ×
106          </button>
107        </div>
108        <div
109          style={{
110            display: "grid",
111            gridTemplateColumns: "auto 1fr",
112            gap: "12px 20px",
113            fontSize: "14px",
114          }}
115        >
116          {usage.model && (
117            <>
118              <div style={{ color: "#6b7280", fontWeight: "500" }}>Model:</div>
119              <div style={{ color: "#1f2937" }}>{usage.model}</div>
120            </>
121          )}
122          <div style={{ color: "#6b7280", fontWeight: "500" }}>Input Tokens:</div>
123          <div style={{ color: "#1f2937" }}>{usage.input_tokens.toLocaleString()}</div>
124          {usage.cache_read_input_tokens > 0 && (
125            <>
126              <div style={{ color: "#6b7280", fontWeight: "500" }}>Cache Read:</div>
127              <div style={{ color: "#1f2937" }}>
128                {usage.cache_read_input_tokens.toLocaleString()}
129              </div>
130            </>
131          )}
132          {usage.cache_creation_input_tokens > 0 && (
133            <>
134              <div style={{ color: "#6b7280", fontWeight: "500" }}>Cache Write:</div>
135              <div style={{ color: "#1f2937" }}>
136                {usage.cache_creation_input_tokens.toLocaleString()}
137              </div>
138            </>
139          )}
140          <div style={{ color: "#6b7280", fontWeight: "500" }}>Output Tokens:</div>
141          <div style={{ color: "#1f2937" }}>{usage.output_tokens.toLocaleString()}</div>
142          {usage.cost_usd > 0 && (
143            <>
144              <div style={{ color: "#6b7280", fontWeight: "500" }}>Cost:</div>
145              <div style={{ color: "#1f2937" }}>${usage.cost_usd.toFixed(4)}</div>
146            </>
147          )}
148          {durationMs !== null && (
149            <>
150              <div style={{ color: "#6b7280", fontWeight: "500" }}>Duration:</div>
151              <div style={{ color: "#1f2937" }}>{formatDuration(durationMs)}</div>
152            </>
153          )}
154          {usage.end_time && (
155            <>
156              <div style={{ color: "#6b7280", fontWeight: "500" }}>Timestamp:</div>
157              <div style={{ color: "#1f2937" }}>{formatTimestamp(usage.end_time)}</div>
158            </>
159          )}
160        </div>
161      </div>
162    </div>
163  );
164}
165
166export default UsageDetailModal;