@sanity/ailf-studio 1.7.0 → 1.7.2

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/index.d.ts CHANGED
@@ -521,6 +521,15 @@ interface JudgmentData {
521
521
  /** Docs the task expected the model to use */
522
522
  canonicalDocs?: DocumentRef[];
523
523
  dimension: string;
524
+ /**
525
+ * `graderJudgments` manifest entry key = `formatEntryKey({mode, task,
526
+ * model, grader})` from the slim-report publisher. Present on reports
527
+ * published under W0051+. Consumed by `useArtifactDetail` in
528
+ * `JudgmentDetailDrawer` to hydrate the full reasoning from GCS, and by
529
+ * `JudgmentList` to drive hover-prefetch and the list row's data hook.
530
+ * Optional so legacy reports (pre-W0051) still type-check.
531
+ */
532
+ id?: string;
524
533
  modelId: string;
525
534
  /** True when the model failed to produce output (empty response, API error, refusal) */
526
535
  outputFailure?: boolean;
package/dist/index.js CHANGED
@@ -3280,7 +3280,7 @@ import {
3280
3280
  TabPanel as TabPanel3,
3281
3281
  Text as Text46
3282
3282
  } from "@sanity/ui";
3283
- import { useCallback as useCallback36 } from "react";
3283
+ import { useCallback as useCallback36, useEffect as useEffect15 } from "react";
3284
3284
  import { useRouter as useRouter3 } from "sanity/router";
3285
3285
 
3286
3286
  // src/lib/help-context.ts
@@ -6064,7 +6064,7 @@ import {
6064
6064
  import {
6065
6065
  useCallback as useCallback32,
6066
6066
  useEffect as useEffect10,
6067
- useMemo as useMemo13,
6067
+ useMemo as useMemo15,
6068
6068
  useState as useState22
6069
6069
  } from "react";
6070
6070
  import { useClient as useClient10 } from "sanity";
@@ -6095,8 +6095,21 @@ function findManifestEntry(ref, key) {
6095
6095
  return ref.entries.find((e) => e.key === key);
6096
6096
  }
6097
6097
 
6098
+ // src/lib/use-test-outputs-artifact.ts
6099
+ import { useMemo as useMemo7 } from "react";
6100
+
6101
+ // src/lib/test-outputs-entry-key.ts
6102
+ var VARIANT_SUFFIX_PATTERN = /\s*\((gold|baseline)\)\s*$/i;
6103
+ function testOutputsEntryKey(taskId, modelId) {
6104
+ const match = VARIANT_SUFFIX_PATTERN.exec(taskId);
6105
+ if (!match) return null;
6106
+ const mode = match[1].toLowerCase();
6107
+ const task = taskId.slice(0, match.index).trim();
6108
+ return `${mode}--${task}--${modelId}`;
6109
+ }
6110
+
6098
6111
  // src/lib/useArtifactCache.ts
6099
- import { useCallback as useCallback15, useRef as useRef5, useState as useState11 } from "react";
6112
+ import { useCallback as useCallback15, useMemo as useMemo6, useRef as useRef5, useState as useState11 } from "react";
6100
6113
  function useArtifactCache(opts) {
6101
6114
  const { runId, artifactRef, type } = opts;
6102
6115
  const cacheRef = useRef5(/* @__PURE__ */ new Map());
@@ -6104,7 +6117,10 @@ function useArtifactCache(opts) {
6104
6117
  const [error, setError] = useState11(null);
6105
6118
  const inFlight2 = useRef5(/* @__PURE__ */ new Set());
6106
6119
  const BULK_KEY = "__bulk__";
6107
- const availableEntries = artifactRef?.entries?.map((e) => e.key) ?? [];
6120
+ const availableEntries = useMemo6(
6121
+ () => artifactRef?.entries?.map((e) => e.key) ?? [],
6122
+ [artifactRef]
6123
+ );
6108
6124
  const fetchEntry = useCallback15(
6109
6125
  async (key) => {
6110
6126
  if (!artifactRef || !runId) return;
@@ -6159,14 +6175,17 @@ function useArtifactCache(opts) {
6159
6175
  (key) => cacheRef.current.get(key) ?? null,
6160
6176
  []
6161
6177
  );
6162
- return {
6163
- status,
6164
- error,
6165
- availableEntries,
6166
- getEntry,
6167
- fetchEntry,
6168
- fetchAll
6169
- };
6178
+ return useMemo6(
6179
+ () => ({
6180
+ status,
6181
+ error,
6182
+ availableEntries,
6183
+ getEntry,
6184
+ fetchEntry,
6185
+ fetchAll
6186
+ }),
6187
+ [status, error, availableEntries, getEntry, fetchEntry, fetchAll]
6188
+ );
6170
6189
  }
6171
6190
  async function signAndFetch(signingUrl) {
6172
6191
  const signingRes = await fetch(signingUrl, {
@@ -6201,24 +6220,27 @@ function useTestOutputsArtifact(runId, artifactRef) {
6201
6220
  artifactRef,
6202
6221
  type: "testOutputs"
6203
6222
  });
6204
- return {
6205
- status: cache.status,
6206
- error: cache.error,
6207
- getOutput: (taskId, modelId) => cache.getEntry(`${taskId}::${modelId}`),
6208
- fetchOutput: async (taskId, modelId) => {
6209
- if (!artifactRef) return;
6210
- if (!artifactRef.layout || artifactRef.layout === "bulk") {
6211
- await cache.fetchAll();
6212
- return;
6223
+ return useMemo7(() => {
6224
+ const entryKey = (taskId, modelId) => testOutputsEntryKey(taskId, modelId) ?? `${taskId}::${modelId}`;
6225
+ return {
6226
+ status: cache.status,
6227
+ error: cache.error,
6228
+ getOutput: (taskId, modelId) => cache.getEntry(entryKey(taskId, modelId)),
6229
+ fetchOutput: async (taskId, modelId) => {
6230
+ if (!artifactRef) return;
6231
+ if (!artifactRef.layout || artifactRef.layout === "bulk") {
6232
+ await cache.fetchAll();
6233
+ return;
6234
+ }
6235
+ await cache.fetchEntry(entryKey(taskId, modelId));
6236
+ },
6237
+ hasOutput: (taskId, modelId) => {
6238
+ if (!artifactRef) return false;
6239
+ if (!artifactRef.layout || artifactRef.layout === "bulk") return true;
6240
+ return cache.availableEntries.includes(entryKey(taskId, modelId));
6213
6241
  }
6214
- await cache.fetchEntry(`${taskId}::${modelId}`);
6215
- },
6216
- hasOutput: (taskId, modelId) => {
6217
- if (!artifactRef) return false;
6218
- if (!artifactRef.layout || artifactRef.layout === "bulk") return true;
6219
- return cache.availableEntries.includes(`${taskId}::${modelId}`);
6220
- }
6221
- };
6242
+ };
6243
+ }, [cache, artifactRef]);
6222
6244
  }
6223
6245
 
6224
6246
  // src/lib/thresholds.ts
@@ -6262,7 +6284,7 @@ function negativeDocLiftSentiment(count) {
6262
6284
  }
6263
6285
 
6264
6286
  // src/components/report-detail/AgentBehaviorCard.tsx
6265
- import { useMemo as useMemo6, useState as useState12 } from "react";
6287
+ import { useMemo as useMemo8, useState as useState12 } from "react";
6266
6288
  import { HelpCircleIcon as HelpCircleIcon6, SearchIcon as SearchIcon4 } from "@sanity/icons";
6267
6289
  import {
6268
6290
  Badge as Badge5,
@@ -6473,7 +6495,7 @@ function SearchQueryList({
6473
6495
  truncated
6474
6496
  }) {
6475
6497
  const [filter, setFilter] = useState12("");
6476
- const filtered = useMemo6(() => {
6498
+ const filtered = useMemo8(() => {
6477
6499
  if (!filter) return queries;
6478
6500
  const lower = filter.toLowerCase();
6479
6501
  return queries.filter((q) => q.toLowerCase().includes(lower));
@@ -7146,7 +7168,7 @@ function Pill({
7146
7168
  import { Box as Box16, Flex as Flex15, Stack as Stack19, Text as Text24 } from "@sanity/ui";
7147
7169
 
7148
7170
  // src/lib/useArtifactListRow.ts
7149
- import { useCallback as useCallback18, useMemo as useMemo8 } from "react";
7171
+ import { useCallback as useCallback18, useMemo as useMemo10 } from "react";
7150
7172
 
7151
7173
  // src/lib/useArtifactDetail.ts
7152
7174
  import { useCallback as useCallback16, useEffect as useEffect7, useState as useState13 } from "react";
@@ -7157,6 +7179,7 @@ function cacheKey({ runId, type, key }) {
7157
7179
  }
7158
7180
  var hydratedEntries = /* @__PURE__ */ new Map();
7159
7181
  var inFlight = /* @__PURE__ */ new Map();
7182
+ var knownNotFound = /* @__PURE__ */ new Set();
7160
7183
  var subscribers = /* @__PURE__ */ new Map();
7161
7184
  function getCached(k) {
7162
7185
  const hit = hydratedEntries.get(cacheKey(k));
@@ -7190,6 +7213,12 @@ function recordInFlight(k, start) {
7190
7213
  inFlight.set(composite, p);
7191
7214
  return p;
7192
7215
  }
7216
+ function markNotFound(k) {
7217
+ knownNotFound.add(cacheKey(k));
7218
+ }
7219
+ function isKnownNotFound(k) {
7220
+ return knownNotFound.has(cacheKey(k));
7221
+ }
7193
7222
  function notify(composite) {
7194
7223
  const set2 = subscribers.get(composite);
7195
7224
  if (!set2) return;
@@ -7197,6 +7226,14 @@ function notify(composite) {
7197
7226
  }
7198
7227
 
7199
7228
  // src/lib/artifact-fetch.ts
7229
+ var ArtifactHttpError = class extends Error {
7230
+ status;
7231
+ constructor(message, status) {
7232
+ super(message);
7233
+ this.name = "ArtifactHttpError";
7234
+ this.status = status;
7235
+ }
7236
+ };
7200
7237
  function buildSigningUrl(runId, type, entryKey) {
7201
7238
  const base = `${ARTIFACT_API_BASE_URL}/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(type)}`;
7202
7239
  return entryKey === void 0 ? base : `${base}/${encodeURIComponent(entryKey)}`;
@@ -7205,8 +7242,9 @@ async function signAndFetchJson(signingUrl) {
7205
7242
  const signed = await exchangeSigningUrl(signingUrl);
7206
7243
  const res = await fetch(signed, { credentials: "omit" });
7207
7244
  if (!res.ok) {
7208
- throw new Error(
7209
- `GCS artifact fetch failed: ${res.status} ${res.statusText}`
7245
+ throw new ArtifactHttpError(
7246
+ `GCS artifact fetch failed: ${res.status} ${res.statusText}`,
7247
+ res.status
7210
7248
  );
7211
7249
  }
7212
7250
  return await res.json();
@@ -7215,8 +7253,9 @@ async function signAndFetchNdjson(signingUrl) {
7215
7253
  const signed = await exchangeSigningUrl(signingUrl);
7216
7254
  const res = await fetch(signed, { credentials: "omit" });
7217
7255
  if (!res.ok) {
7218
- throw new Error(
7219
- `GCS artifact fetch failed: ${res.status} ${res.statusText}`
7256
+ throw new ArtifactHttpError(
7257
+ `GCS artifact fetch failed: ${res.status} ${res.statusText}`,
7258
+ res.status
7220
7259
  );
7221
7260
  }
7222
7261
  const body = await res.text();
@@ -7271,8 +7310,9 @@ async function exchangeSigningUrl(signingUrl) {
7271
7310
  });
7272
7311
  if (!res.ok) {
7273
7312
  const body = await res.text().catch(() => "");
7274
- throw new Error(
7275
- `Artifact signing failed: ${res.status} ${res.statusText}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
7313
+ throw new ArtifactHttpError(
7314
+ `Artifact signing failed: ${res.status} ${res.statusText}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`,
7315
+ res.status
7276
7316
  );
7277
7317
  }
7278
7318
  const envelope = await res.json();
@@ -7304,21 +7344,29 @@ function useArtifactDetail(type, key) {
7304
7344
  }, [runId, type, key]);
7305
7345
  const request = useCallback16(async () => {
7306
7346
  if (!runId || !ref || !key) return;
7307
- const hit = getCached({ runId, type, key });
7347
+ const cacheId = { runId, type, key };
7348
+ if (isKnownNotFound(cacheId)) {
7349
+ setStatus("error");
7350
+ return;
7351
+ }
7352
+ const hit = getCached(cacheId);
7308
7353
  if (hit !== null) {
7309
7354
  setStatus("ready");
7310
7355
  return;
7311
7356
  }
7312
7357
  setStatus("loading");
7313
7358
  try {
7314
- await recordInFlight({ runId, type, key }, async () => {
7359
+ await recordInFlight(cacheId, async () => {
7315
7360
  const url = buildSigningUrl(runId, type, key);
7316
7361
  const body = NDJSON_TYPES.has(type) ? await signAndFetchNdjson(url) : await signAndFetchJson(url);
7317
- setCached({ runId, type, key }, body);
7362
+ setCached(cacheId, body);
7318
7363
  return body;
7319
7364
  });
7320
7365
  setStatus("ready");
7321
- } catch {
7366
+ } catch (err) {
7367
+ if (err instanceof ArtifactHttpError && err.status === 404) {
7368
+ markNotFound(cacheId);
7369
+ }
7322
7370
  setStatus("error");
7323
7371
  }
7324
7372
  }, [runId, ref, type, key]);
@@ -7349,37 +7397,77 @@ function useArtifactEntry(type, key) {
7349
7397
  }
7350
7398
 
7351
7399
  // src/lib/useArtifactPrefetch.ts
7352
- import { useCallback as useCallback17, useMemo as useMemo7 } from "react";
7400
+ import { useCallback as useCallback17, useMemo as useMemo9 } from "react";
7353
7401
  var NDJSON_TYPES2 = /* @__PURE__ */ new Set([
7354
7402
  "traces"
7355
7403
  ]);
7404
+ var HOVER_DEBOUNCE_MS = 200;
7405
+ var MAX_CONCURRENT_PREFETCHES = 4;
7406
+ var pendingHoverTimers = /* @__PURE__ */ new Map();
7407
+ var activePrefetches = 0;
7408
+ var prefetchWaitQueue = [];
7409
+ async function withPrefetchSlot(run) {
7410
+ if (activePrefetches >= MAX_CONCURRENT_PREFETCHES) {
7411
+ await new Promise((resolve) => prefetchWaitQueue.push(resolve));
7412
+ }
7413
+ activePrefetches++;
7414
+ try {
7415
+ return await run();
7416
+ } finally {
7417
+ activePrefetches--;
7418
+ const next = prefetchWaitQueue.shift();
7419
+ if (next) next();
7420
+ }
7421
+ }
7422
+ async function prefetchArtifactCore(args) {
7423
+ const { runId, type, key } = args;
7424
+ if (!runId || !key) return;
7425
+ const cacheId = { runId, type, key };
7426
+ if (isKnownNotFound(cacheId)) return;
7427
+ if (getCached(cacheId) !== null) return;
7428
+ try {
7429
+ await recordInFlight(
7430
+ cacheId,
7431
+ () => withPrefetchSlot(async () => {
7432
+ const url = buildSigningUrl(runId, type, key);
7433
+ const body = NDJSON_TYPES2.has(type) ? await signAndFetchNdjson(url) : await signAndFetchJson(url);
7434
+ setCached(cacheId, body);
7435
+ return body;
7436
+ })
7437
+ );
7438
+ } catch (err) {
7439
+ if (err instanceof ArtifactHttpError && err.status === 404) {
7440
+ markNotFound(cacheId);
7441
+ }
7442
+ }
7443
+ }
7444
+ function scheduleHoverPrefetch(type, run, debounceMs = HOVER_DEBOUNCE_MS) {
7445
+ const existing = pendingHoverTimers.get(type);
7446
+ if (existing) clearTimeout(existing);
7447
+ const timer = setTimeout(() => {
7448
+ pendingHoverTimers.delete(type);
7449
+ run();
7450
+ }, debounceMs);
7451
+ pendingHoverTimers.set(type, timer);
7452
+ }
7356
7453
  function useArtifactPrefetch(type) {
7357
7454
  const { runId } = useReportArtifactContext();
7358
7455
  const ref = useArtifactRef(type);
7359
7456
  const prefetch = useCallback17(
7360
7457
  async (key) => {
7361
7458
  if (!runId || !ref || !key) return;
7362
- const cached = getCached({ runId, type, key });
7363
- if (cached !== null) return;
7364
- try {
7365
- await recordInFlight({ runId, type, key }, async () => {
7366
- const url = buildSigningUrl(runId, type, key);
7367
- const body = NDJSON_TYPES2.has(type) ? await signAndFetchNdjson(url) : await signAndFetchJson(url);
7368
- setCached({ runId, type, key }, body);
7369
- return body;
7370
- });
7371
- } catch {
7372
- }
7459
+ await prefetchArtifactCore({ runId, type, key });
7373
7460
  },
7374
7461
  [runId, ref, type]
7375
7462
  );
7376
7463
  const onHover = useCallback17(
7377
7464
  (key) => {
7378
7465
  return () => {
7379
- void prefetch(key);
7466
+ if (!runId || !ref || !key) return;
7467
+ scheduleHoverPrefetch(type, () => void prefetch(key));
7380
7468
  };
7381
7469
  },
7382
- [prefetch]
7470
+ [runId, ref, type, prefetch]
7383
7471
  );
7384
7472
  const prefetchWindow = useCallback17(
7385
7473
  async (keys, centerIndex, radius = 5) => {
@@ -7413,7 +7501,7 @@ function useArtifactPrefetch(type) {
7413
7501
  },
7414
7502
  [runId, ref, type]
7415
7503
  );
7416
- return useMemo7(
7504
+ return useMemo9(
7417
7505
  () => ({ prefetch, onHover, prefetchWindow, warmAll }),
7418
7506
  [prefetch, onHover, prefetchWindow, warmAll]
7419
7507
  );
@@ -7446,7 +7534,7 @@ function useArtifactListRow(type, key, listKeys) {
7446
7534
  const entry = useArtifactEntry(type, key);
7447
7535
  const [full, status, request] = useArtifactDetail(type, key);
7448
7536
  const { onHover, prefetchWindow } = useArtifactPrefetch(type);
7449
- const onMouseEnter = useMemo8(() => onHover(key), [onHover, key]);
7537
+ const onMouseEnter = useMemo10(() => onHover(key), [onHover, key]);
7450
7538
  const onFocus = useCallback18(() => {
7451
7539
  const idx = listKeys.indexOf(key);
7452
7540
  if (idx < 0) return;
@@ -7720,7 +7808,7 @@ function formatShortDate(iso) {
7720
7808
  }
7721
7809
 
7722
7810
  // src/components/report-detail/JudgmentList.tsx
7723
- import React2, { useCallback as useCallback21, useEffect as useEffect9, useMemo as useMemo9, useRef as useRef6, useState as useState15 } from "react";
7811
+ import React2, { useCallback as useCallback21, useEffect as useEffect9, useMemo as useMemo11, useRef as useRef6, useState as useState15 } from "react";
7724
7812
  import {
7725
7813
  ChevronDownIcon,
7726
7814
  ChevronRightIcon,
@@ -8070,11 +8158,7 @@ function testResultKey(taskId, dimension, modelId) {
8070
8158
  return `${taskId}::${dimension}::${modelId}`;
8071
8159
  }
8072
8160
  function testOutputsKeyFor(j) {
8073
- const match = /\s*\((gold|baseline)\)\s*$/i.exec(j.taskId);
8074
- if (!match) return null;
8075
- const mode = match[1].toLowerCase();
8076
- const task = j.taskId.slice(0, match.index).trim();
8077
- return `${mode}--${task}--${j.modelId}`;
8161
+ return testOutputsEntryKey(j.taskId, j.modelId);
8078
8162
  }
8079
8163
  function JudgmentList({
8080
8164
  artifactCache,
@@ -8089,7 +8173,7 @@ function JudgmentList({
8089
8173
  );
8090
8174
  const [scoreBand, setScoreBand] = useState15(null);
8091
8175
  const [sortOrder, setSortOrder] = useState15("score-desc");
8092
- const testResultMap = useMemo9(() => {
8176
+ const testResultMap = useMemo11(() => {
8093
8177
  const map = /* @__PURE__ */ new Map();
8094
8178
  if (!testResults) return map;
8095
8179
  for (const tr of testResults) {
@@ -8099,15 +8183,15 @@ function JudgmentList({
8099
8183
  }
8100
8184
  return map;
8101
8185
  }, [testResults]);
8102
- const pruned = useMemo9(() => pruneNoise(judgments), [judgments]);
8103
- const dimensionOptions = useMemo9(() => {
8186
+ const pruned = useMemo11(() => pruneNoise(judgments), [judgments]);
8187
+ const dimensionOptions = useMemo11(() => {
8104
8188
  const counts = /* @__PURE__ */ new Map();
8105
8189
  for (const j of pruned) {
8106
8190
  counts.set(j.dimension, (counts.get(j.dimension) ?? 0) + 1);
8107
8191
  }
8108
8192
  return [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, count]) => ({ key, label: dimensionLabel2(key), count }));
8109
8193
  }, [pruned]);
8110
- const filtered = useMemo9(() => {
8194
+ const filtered = useMemo11(() => {
8111
8195
  return pruned.filter((j) => {
8112
8196
  if (selectedDimensions.size > 0 && !selectedDimensions.has(j.dimension))
8113
8197
  return false;
@@ -8116,17 +8200,17 @@ function JudgmentList({
8116
8200
  return true;
8117
8201
  });
8118
8202
  }, [pruned, selectedDimensions, scoreBand, query]);
8119
- const grouped = useMemo9(
8203
+ const grouped = useMemo11(
8120
8204
  () => groupByArea(filtered, sortOrder),
8121
8205
  [filtered, sortOrder]
8122
8206
  );
8123
- const groupedWithStats = useMemo9(
8207
+ const groupedWithStats = useMemo11(
8124
8208
  () => grouped.map(
8125
8209
  ([area, list]) => [area, list, computeGroupStats(list)]
8126
8210
  ),
8127
8211
  [grouped]
8128
8212
  );
8129
- const focusedJudgment = useMemo9(() => {
8213
+ const focusedJudgment = useMemo11(() => {
8130
8214
  if (!focus) return null;
8131
8215
  return pruned.find((j) => judgmentSlug(j) === focus) ?? null;
8132
8216
  }, [pruned, focus]);
@@ -8172,13 +8256,13 @@ function JudgmentList({
8172
8256
  });
8173
8257
  }, [focusedJudgment]);
8174
8258
  const [activeRowSlug, setActiveRowSlug] = useState15(null);
8175
- const flatSlugs = useMemo9(
8259
+ const flatSlugs = useMemo11(
8176
8260
  () => grouped.flatMap(
8177
8261
  ([, areaJudgments]) => areaJudgments.map((j) => judgmentSlug(j))
8178
8262
  ),
8179
8263
  [grouped]
8180
8264
  );
8181
- const flatKeys = useMemo9(
8265
+ const flatKeys = useMemo11(
8182
8266
  () => grouped.flatMap(
8183
8267
  ([, areaJudgments]) => areaJudgments.map((j) => j.id ?? "")
8184
8268
  ),
@@ -8194,12 +8278,15 @@ function JudgmentList({
8194
8278
  }
8195
8279
  }, [flatSlugs, activeRowSlug]);
8196
8280
  const { close: closeDrawer, open: openDrawer } = useJudgmentDrawer();
8281
+ const { runId: artifactRunId, manifest: artifactManifest } = useReportArtifactContext();
8197
8282
  useEffect9(() => {
8198
8283
  if (focus && focusedJudgment) {
8199
8284
  openDrawer({
8200
8285
  artifactCache,
8201
8286
  judgment: focusedJudgment,
8202
- testResult: focusedTestResult
8287
+ testResult: focusedTestResult,
8288
+ runId: artifactRunId,
8289
+ manifest: artifactManifest
8203
8290
  });
8204
8291
  } else if (!focus) {
8205
8292
  closeDrawer();
@@ -8210,7 +8297,9 @@ function JudgmentList({
8210
8297
  focusedTestResult,
8211
8298
  artifactCache,
8212
8299
  openDrawer,
8213
- closeDrawer
8300
+ closeDrawer,
8301
+ artifactRunId,
8302
+ artifactManifest
8214
8303
  ]);
8215
8304
  const prevFocusRef = useRef6(focus);
8216
8305
  useEffect9(() => {
@@ -8512,14 +8601,18 @@ function JudgmentCard({
8512
8601
  },
8513
8602
  [handleClick]
8514
8603
  );
8604
+ const onHoverTestOutputs = useMemo11(
8605
+ () => testOutputsKey ? testOutputsPrefetch.onHover(testOutputsKey) : null,
8606
+ [testOutputsPrefetch, testOutputsKey]
8607
+ );
8515
8608
  const handleMouseEnter = useCallback21(() => {
8516
8609
  row.handlers.onMouseEnter();
8517
- if (testOutputsKey) void testOutputsPrefetch.prefetch(testOutputsKey);
8518
- }, [row.handlers, testOutputsPrefetch, testOutputsKey]);
8610
+ onHoverTestOutputs?.();
8611
+ }, [row.handlers, onHoverTestOutputs]);
8519
8612
  const handleFocusPrefetch = useCallback21(() => {
8520
8613
  row.handlers.onFocus();
8521
- if (testOutputsKey) void testOutputsPrefetch.prefetch(testOutputsKey);
8522
- }, [row.handlers, testOutputsPrefetch, testOutputsKey]);
8614
+ onHoverTestOutputs?.();
8615
+ }, [row.handlers, onHoverTestOutputs]);
8523
8616
  return /* @__PURE__ */ jsx32(
8524
8617
  Box18,
8525
8618
  {
@@ -9429,14 +9522,14 @@ function ReportHeader({
9429
9522
  }
9430
9523
 
9431
9524
  // src/components/report-detail/StrengthsList.tsx
9432
- import { useMemo as useMemo12 } from "react";
9525
+ import { useMemo as useMemo14 } from "react";
9433
9526
  import { CheckmarkCircleIcon as CheckmarkCircleIcon2, SearchIcon as SearchIcon7 } from "@sanity/icons";
9434
9527
  import { Box as Box21, Flex as Flex25, Stack as Stack26, Text as Text34 } from "@sanity/ui";
9435
9528
 
9436
9529
  // src/components/report-detail/AreaScoresGrid.tsx
9437
9530
  import React3, {
9438
9531
  useCallback as useCallback29,
9439
- useMemo as useMemo10,
9532
+ useMemo as useMemo12,
9440
9533
  useState as useState20
9441
9534
  } from "react";
9442
9535
  import { WarningOutlineIcon as WarningOutlineIcon2 } from "@sanity/icons";
@@ -9481,12 +9574,12 @@ function AreaScoresGrid({
9481
9574
  }) {
9482
9575
  const { ref: containerRef, width } = useContainerWidth();
9483
9576
  const tier = tableTier(width);
9484
- const hasActual = useMemo10(
9577
+ const hasActual = useMemo12(
9485
9578
  () => scores.some((s) => s.actualScore != null),
9486
9579
  [scores]
9487
9580
  );
9488
9581
  const showLift = isLiteracyMode(mode);
9489
- const dimKeys = useMemo10(() => collectDimensionKeys(scores), [scores]);
9582
+ const dimKeys = useMemo12(() => collectDimensionKeys(scores), [scores]);
9490
9583
  const [sortField, setSortField] = useState20("score");
9491
9584
  const [sortDir, setSortDir] = useState20("desc");
9492
9585
  const handleSort = useCallback29(
@@ -9500,7 +9593,7 @@ function AreaScoresGrid({
9500
9593
  },
9501
9594
  [sortField]
9502
9595
  );
9503
- const sorted = useMemo10(() => {
9596
+ const sorted = useMemo12(() => {
9504
9597
  const dir = sortDir === "asc" ? 1 : -1;
9505
9598
  return [...scores].sort((a, b) => {
9506
9599
  switch (sortField) {
@@ -9515,7 +9608,7 @@ function AreaScoresGrid({
9515
9608
  }
9516
9609
  });
9517
9610
  }, [scores, sortField, sortDir]);
9518
- const modelScoresByFeature = useMemo10(() => {
9611
+ const modelScoresByFeature = useMemo12(() => {
9519
9612
  if (!perModel) return null;
9520
9613
  const map = /* @__PURE__ */ new Map();
9521
9614
  for (const model of perModel) {
@@ -10127,7 +10220,7 @@ function Pill2({
10127
10220
  }
10128
10221
 
10129
10222
  // src/components/report-detail/useModelSelection.ts
10130
- import { useCallback as useCallback31, useMemo as useMemo11, useState as useState21 } from "react";
10223
+ import { useCallback as useCallback31, useMemo as useMemo13, useState as useState21 } from "react";
10131
10224
  function useModelSelection({
10132
10225
  scores,
10133
10226
  perModel
@@ -10138,7 +10231,7 @@ function useModelSelection({
10138
10231
  }, []);
10139
10232
  const isExpanded = selection === "expanded";
10140
10233
  const hasModels = perModel != null && perModel.length > 1;
10141
- const resolvedScores = useMemo11(() => {
10234
+ const resolvedScores = useMemo13(() => {
10142
10235
  if (isExpanded || selection === null || !perModel) return scores;
10143
10236
  const model = perModel.find((m) => m.modelId === selection);
10144
10237
  return model?.scores ?? scores;
@@ -10169,7 +10262,7 @@ function StrengthsList({
10169
10262
  hasModels,
10170
10263
  expandedPerModel
10171
10264
  } = useModelSelection({ scores, perModel });
10172
- const displayedScores = useMemo12(
10265
+ const displayedScores = useMemo14(
10173
10266
  () => resolvedScores.filter((s) => s.totalScore >= SCORE_CAUTION),
10174
10267
  [resolvedScores]
10175
10268
  );
@@ -10667,17 +10760,17 @@ function ReportDetail({
10667
10760
  const hasAgentActivity = Boolean(
10668
10761
  summary?.agentBehavior && summary.agentBehavior.length > 0
10669
10762
  );
10670
- const tabs = useMemo13(
10763
+ const tabs = useMemo15(
10671
10764
  () => [OVERVIEW_TAB, DIAGNOSTICS_TAB, ACTIVITY_TAB],
10672
10765
  []
10673
10766
  );
10674
- const disabledTabs = useMemo13(() => {
10767
+ const disabledTabs = useMemo15(() => {
10675
10768
  const set2 = /* @__PURE__ */ new Set();
10676
10769
  if (!hasDiagnostics) set2.add("diagnostics");
10677
10770
  if (!hasAgentActivity) set2.add("activity");
10678
10771
  return set2;
10679
10772
  }, [hasDiagnostics, hasAgentActivity]);
10680
- const currentTab = useMemo13(() => {
10773
+ const currentTab = useMemo15(() => {
10681
10774
  const parsed = parseTab(activeTab);
10682
10775
  if (disabledTabs.has(parsed)) return "overview";
10683
10776
  return tabs.some((t) => t.id === parsed) ? parsed : "overview";
@@ -11158,11 +11251,12 @@ var SECTION_LABEL_STYLE = {
11158
11251
  textTransform: "uppercase"
11159
11252
  };
11160
11253
  var COPYABLE_BLOCK_STYLE = {
11161
- paddingRight: 40
11254
+ position: "relative"
11162
11255
  };
11163
11256
  var COPY_BUTTON_SLOT_STYLE = {
11164
11257
  float: "right",
11165
11258
  marginLeft: 8,
11259
+ marginBottom: 4,
11166
11260
  position: "sticky",
11167
11261
  top: 0,
11168
11262
  zIndex: 1
@@ -11222,9 +11316,23 @@ function JudgmentDetailDrawer({
11222
11316
  const rawTaskName = sep > 0 ? judgment.taskId.substring(sep + 3) : judgment.taskId;
11223
11317
  const { name: taskName, variant } = splitVariant(rawTaskName);
11224
11318
  const dimLabel = dimensionLabel2(judgment.dimension);
11319
+ const judgmentId = judgment.id ?? "";
11320
+ const [fullJudgment, fullStatus, requestFullJudgment] = useArtifactDetail("graderJudgments", judgmentId);
11321
+ useEffect12(() => {
11322
+ if (!judgmentId) return;
11323
+ if (fullStatus !== "idle") return;
11324
+ void requestFullJudgment();
11325
+ }, [judgmentId, fullStatus, requestFullJudgment]);
11326
+ const fullReason = typeof fullJudgment?.reason === "string" && fullJudgment.reason.length > 0 ? fullJudgment.reason : null;
11327
+ const reasoningText = fullReason ?? judgment.reason;
11328
+ const reasoningIsPreview = fullReason === null && judgmentId.length > 0 && fullStatus !== "error";
11225
11329
  useEffect12(() => {
11226
11330
  setTab("reasoning");
11227
11331
  }, [judgment.taskId, judgment.dimension, judgment.modelId]);
11332
+ const [fetchDispatched, setFetchDispatched] = useState24(false);
11333
+ useEffect12(() => {
11334
+ setFetchDispatched(false);
11335
+ }, [judgment.taskId, judgment.dimension, judgment.modelId]);
11228
11336
  const inlineOutput = testResult?.responseOutput;
11229
11337
  const artifactEntry = artifactCache?.getOutput(
11230
11338
  judgment.taskId,
@@ -11232,19 +11340,21 @@ function JudgmentDetailDrawer({
11232
11340
  );
11233
11341
  const resolvedOutput = inlineOutput ?? artifactEntry?.responseOutput ?? null;
11234
11342
  const resolvedTruncated = testResult?.responseOutputTruncated ?? artifactEntry?.responseOutputTruncated ?? false;
11235
- const entryKnownToManifest = artifactCache?.hasOutput(judgment.taskId, judgment.modelId) ?? false;
11236
- const canFetchArtifact = !inlineOutput && !artifactEntry && artifactCache != null && entryKnownToManifest;
11237
- const entryUnavailable = !inlineOutput && !artifactEntry && artifactCache != null && !entryKnownToManifest;
11238
- const hasOutputTab = resolvedOutput != null || canFetchArtifact || entryUnavailable;
11343
+ const canAttemptFetch = !inlineOutput && !artifactEntry && artifactCache != null;
11344
+ const fetchInFlight = canAttemptFetch && artifactCache?.status === "loading";
11345
+ const fetchFailed = canAttemptFetch && artifactCache?.status === "error";
11346
+ const entryUnavailable = canAttemptFetch && fetchDispatched && !fetchInFlight && !fetchFailed;
11347
+ const hasOutputTab = resolvedOutput != null || canAttemptFetch;
11239
11348
  useEffect12(() => {
11240
- if (tab === "output" && !resolvedOutput && artifactCache && entryKnownToManifest) {
11241
- artifactCache.fetchOutput(judgment.taskId, judgment.modelId);
11349
+ if (tab === "output" && canAttemptFetch && !fetchDispatched) {
11350
+ setFetchDispatched(true);
11351
+ void artifactCache.fetchOutput(judgment.taskId, judgment.modelId);
11242
11352
  }
11243
11353
  }, [
11244
11354
  tab,
11245
- resolvedOutput,
11355
+ canAttemptFetch,
11356
+ fetchDispatched,
11246
11357
  artifactCache,
11247
- entryKnownToManifest,
11248
11358
  judgment.taskId,
11249
11359
  judgment.modelId
11250
11360
  ]);
@@ -11453,10 +11563,11 @@ function JudgmentDetailDrawer({
11453
11563
  {
11454
11564
  copiedLabel: "Reasoning copied",
11455
11565
  label: "Copy reasoning",
11456
- text: judgment.reason
11566
+ text: reasoningText
11457
11567
  }
11458
11568
  ) }),
11459
- /* @__PURE__ */ jsx59(Markdown, { content: judgment.reason })
11569
+ /* @__PURE__ */ jsx59(Markdown, { content: reasoningText }),
11570
+ reasoningIsPreview && /* @__PURE__ */ jsx59(Text44, { muted: true, size: 1, style: { marginTop: 8 }, children: fullStatus === "loading" ? "Loading full reasoning\u2026" : "Showing preview (280 chars). Full reasoning not yet loaded." })
11460
11571
  ] })
11461
11572
  }
11462
11573
  ),
@@ -11467,12 +11578,12 @@ function JudgmentDetailDrawer({
11467
11578
  hidden: tab !== "output",
11468
11579
  id: "judgment-panel-output",
11469
11580
  children: [
11470
- !resolvedOutput && entryUnavailable && /* @__PURE__ */ jsx59(Text44, { muted: true, size: 1, children: "Model output not available for this entry." }),
11471
- !resolvedOutput && !entryUnavailable && artifactCache?.status === "loading" && /* @__PURE__ */ jsx59(Text44, { muted: true, size: 1, children: "Fetching model output\u2026" }),
11472
- !resolvedOutput && !entryUnavailable && artifactCache?.status === "error" && /* @__PURE__ */ jsxs42(Text44, { muted: true, size: 1, style: { color: "#f87171" }, children: [
11581
+ !resolvedOutput && fetchInFlight && /* @__PURE__ */ jsx59(Text44, { muted: true, size: 1, children: "Fetching model output\u2026" }),
11582
+ !resolvedOutput && fetchFailed && /* @__PURE__ */ jsxs42(Text44, { muted: true, size: 1, style: { color: "#f87171" }, children: [
11473
11583
  "Failed to load model output",
11474
- artifactCache.error ? `: ${artifactCache.error}` : ""
11584
+ artifactCache?.error ? `: ${artifactCache.error}` : ""
11475
11585
  ] }),
11586
+ !resolvedOutput && entryUnavailable && /* @__PURE__ */ jsx59(Text44, { muted: true, size: 1, children: "Model output not available for this entry." }),
11476
11587
  resolvedOutput && /* @__PURE__ */ jsxs42(Box28, { style: COPYABLE_BLOCK_STYLE, children: [
11477
11588
  /* @__PURE__ */ jsx59(Box28, { style: COPY_BUTTON_SLOT_STYLE, children: /* @__PURE__ */ jsx59(
11478
11589
  CopyButton,
@@ -11629,7 +11740,7 @@ function JudgmentDetailDrawerOutlet({
11629
11740
  flexDirection: "column",
11630
11741
  overflow: "hidden"
11631
11742
  },
11632
- children: /* @__PURE__ */ jsx60(
11743
+ children: /* @__PURE__ */ jsx60(ReportArtifactProvider, { runId: active.runId, manifest: active.manifest, children: /* @__PURE__ */ jsx60(
11633
11744
  JudgmentDetailDrawer,
11634
11745
  {
11635
11746
  artifactCache: active.artifactCache,
@@ -11637,7 +11748,7 @@ function JudgmentDetailDrawerOutlet({
11637
11748
  onClose,
11638
11749
  testResult: active.testResult
11639
11750
  }
11640
- )
11751
+ ) })
11641
11752
  }
11642
11753
  )
11643
11754
  ]
@@ -11647,7 +11758,7 @@ function JudgmentDetailDrawerOutlet({
11647
11758
 
11648
11759
  // src/components/ScoreTimeline.tsx
11649
11760
  import { Card as Card21, Flex as Flex33, Select as Select2, Stack as Stack35, Text as Text45 } from "@sanity/ui";
11650
- import { useCallback as useCallback35, useEffect as useEffect14, useMemo as useMemo14, useState as useState26 } from "react";
11761
+ import { useCallback as useCallback35, useEffect as useEffect14, useMemo as useMemo16, useState as useState26 } from "react";
11651
11762
  import { useClient as useClient11 } from "sanity";
11652
11763
  import { jsx as jsx61, jsxs as jsxs44 } from "react/jsx-runtime";
11653
11764
  var CHART_HEIGHT = 220;
@@ -11688,7 +11799,7 @@ function ScoreTimeline({ mode = null, source = null }) {
11688
11799
  const [loading, setLoading] = useState26(true);
11689
11800
  const [rangeDays, setRangeDays] = useState26(30);
11690
11801
  const [selectedArea, setSelectedArea] = useState26(null);
11691
- const areaNames = useMemo14(() => {
11802
+ const areaNames = useMemo16(() => {
11692
11803
  const names = /* @__PURE__ */ new Set();
11693
11804
  for (const dp of dataPoints) {
11694
11805
  for (const s of dp.scores) {
@@ -11715,7 +11826,7 @@ function ScoreTimeline({ mode = null, source = null }) {
11715
11826
  useEffect14(() => {
11716
11827
  void fetchData();
11717
11828
  }, [fetchData]);
11718
- const chartPoints = useMemo14(() => {
11829
+ const chartPoints = useMemo16(() => {
11719
11830
  const pts = [];
11720
11831
  const scored = dataPoints.map((dp) => ({
11721
11832
  date: dp.completedAt,
@@ -11729,7 +11840,7 @@ function ScoreTimeline({ mode = null, source = null }) {
11729
11840
  });
11730
11841
  return pts;
11731
11842
  }, [dataPoints, selectedArea]);
11732
- const avgScore = useMemo14(() => {
11843
+ const avgScore = useMemo16(() => {
11733
11844
  if (chartPoints.length === 0) return 0;
11734
11845
  return chartPoints.reduce((sum, p) => sum + p.score, 0) / chartPoints.length;
11735
11846
  }, [chartPoints]);
@@ -11873,17 +11984,29 @@ var VIEW_PARAM_MAP = {
11873
11984
  timeline: "timeline"
11874
11985
  };
11875
11986
  function Dashboard() {
11987
+ return /* @__PURE__ */ jsx62(HelpProvider, { children: /* @__PURE__ */ jsx62(JudgmentDrawerProvider, { children: /* @__PURE__ */ jsx62(DashboardShell, {}) }) });
11988
+ }
11989
+ function DashboardShell() {
11876
11990
  const router = useRouter3();
11991
+ const { close: closeDrawer } = useJudgmentDrawer();
11992
+ const routerState = router.state;
11993
+ const reportId = routerState.reportId ?? null;
11994
+ useEffect15(() => {
11995
+ if (!reportId) closeDrawer();
11996
+ }, [reportId, closeDrawer]);
11877
11997
  const handleJudgmentDrawerClose = useCallback36(() => {
11998
+ closeDrawer();
11878
11999
  const state = { ...router.state };
11879
- delete state.focus;
11880
- router.navigate(state);
11881
- }, [router]);
11882
- return /* @__PURE__ */ jsx62(HelpProvider, { children: /* @__PURE__ */ jsx62(JudgmentDrawerProvider, { children: /* @__PURE__ */ jsxs45(Flex34, { style: { height: "100%" }, children: [
12000
+ if (state.focus) {
12001
+ delete state.focus;
12002
+ router.navigate(state);
12003
+ }
12004
+ }, [closeDrawer, router]);
12005
+ return /* @__PURE__ */ jsxs45(Flex34, { style: { height: "100%" }, children: [
11883
12006
  /* @__PURE__ */ jsx62(Box29, { flex: 1, overflow: "auto", children: /* @__PURE__ */ jsx62(DashboardContent, {}) }),
11884
12007
  /* @__PURE__ */ jsx62(JudgmentDetailDrawerOutlet, { onClose: handleJudgmentDrawerClose }),
11885
12008
  /* @__PURE__ */ jsx62(HelpDrawer, {})
11886
- ] }) }) });
12009
+ ] });
11887
12010
  }
11888
12011
  function DashboardContent() {
11889
12012
  const router = useRouter3();
@@ -12016,7 +12139,7 @@ function ailfTool(options = {}) {
12016
12139
  // src/actions/RunEvaluationAction.tsx
12017
12140
  import { BarChartIcon as BarChartIcon2 } from "@sanity/icons";
12018
12141
  import { useToast as useToast10 } from "@sanity/ui";
12019
- import { useCallback as useCallback37, useEffect as useEffect15, useRef as useRef9, useState as useState27 } from "react";
12142
+ import { useCallback as useCallback37, useEffect as useEffect16, useRef as useRef9, useState as useState27 } from "react";
12020
12143
  import {
12021
12144
  getReleaseIdFromReleaseDocumentId as getReleaseIdFromReleaseDocumentId3,
12022
12145
  useClient as useClient12,
@@ -12050,7 +12173,7 @@ function createRunEvaluationAction(options = {}) {
12050
12173
  const [state, setState] = useState27({ status: "loading" });
12051
12174
  const requestedAtRef = useRef9(null);
12052
12175
  const perspectiveId = getReleaseIdFromReleaseDocumentId3(release._id);
12053
- useEffect15(() => {
12176
+ useEffect16(() => {
12054
12177
  let cancelled = false;
12055
12178
  client.fetch(contentImpactQuery, buildReportQueryParams(perspectiveId)).then((results) => {
12056
12179
  if (cancelled) return;
@@ -12073,7 +12196,7 @@ function createRunEvaluationAction(options = {}) {
12073
12196
  cancelled = true;
12074
12197
  };
12075
12198
  }, [client, perspectiveId]);
12076
- useEffect15(() => {
12199
+ useEffect16(() => {
12077
12200
  if (state.status !== "requested" && state.status !== "polling") return;
12078
12201
  const { requestId, startedAt } = state;
12079
12202
  if (state.status === "requested") {
@@ -12123,7 +12246,7 @@ function createRunEvaluationAction(options = {}) {
12123
12246
  }, POLL_INTERVAL_MS2);
12124
12247
  return () => clearInterval(interval);
12125
12248
  }, [client, perspectiveId, state]);
12126
- useEffect15(() => {
12249
+ useEffect16(() => {
12127
12250
  if (state.status !== "error") return;
12128
12251
  const timer = setTimeout(() => {
12129
12252
  client.fetch(contentImpactQuery, buildReportQueryParams(perspectiveId)).then((results) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf-studio",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "AI Literacy Framework — Sanity Studio dashboard plugin",
5
5
  "type": "module",
6
6
  "license": "MIT",