File size: 10,444 Bytes
9b4cdbb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
"use client"

import { useMemo, useState } from "react"
import {
  Copy,
  ExternalLink,
  FileSearch,
  Flag,
  GitPullRequestArrow,
  MessageSquare,
} from "lucide-react"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

interface FlagScoreButtonProps {
  modelName: string
  modelId: string
  benchmarkName: string
  benchmarkId?: string
  score: number | string
  /** URL to the published source the score was extracted from (paper, blog, leaderboard). */
  sourceUrl?: string
  /** URL to the processed record JSON in the card_backend HF dataset. */
  sourceRecordUrl?: string
  /** Optional explicit upstream record URL in evaleval/EEE_datastore (raw source of truth). */
  eeeRecordUrl?: string
}

interface DatasetLinks {
  repoSlug: string
  recordViewUrl: string | null
  recordRawUrl: string | null
  discussionsUrl: string
  newDiscussionUrl: string
}

/**
 * Given a /resolve/main/... HF dataset URL, derive the helpful sibling URLs
 * for the dataset (file viewer, discussions list, new-discussion form). Returns
 * null when the input doesn't look like a HF dataset URL.
 */
function deriveDatasetLinks(recordUrl: string | undefined, prefilledTitle: string): DatasetLinks | null {
  if (!recordUrl) return null
  const m = recordUrl.match(
    /^https:\/\/huggingface\.co\/datasets\/([^/]+\/[^/]+)\/(?:resolve|raw|blob)\/[^/]+\/(.*)$/,
  )
  if (!m) return null
  const repoSlug = m[1]
  const path = m[2]
  const datasetBase = `https://huggingface.co/datasets/${repoSlug}`
  const recordViewUrl = `${datasetBase}/blob/main/${path}`
  const recordRawUrl = `${datasetBase}/resolve/main/${path}`
  const discussionsUrl = `${datasetBase}/discussions`
  const newDiscussionUrl = `${datasetBase}/discussions/new?title=${encodeURIComponent(prefilledTitle)}`
  return { repoSlug, recordViewUrl, recordRawUrl, discussionsUrl, newDiscussionUrl }
}

/**
 * "Flag this score" — researcher affordance that, instead of capturing the
 * report ourselves, sends the user directly to the upstream HF dataset where
 * the data lives. They can then file a discussion or submit a correction PR
 * against the actual record. We also offer a copyable context snippet so the
 * issue body has all the relevant identifiers without needing to retype them.
 */
export function FlagScoreButton({
  modelName,
  modelId,
  benchmarkName,
  benchmarkId,
  score,
  sourceUrl,
  sourceRecordUrl,
  eeeRecordUrl,
}: FlagScoreButtonProps) {
  const [open, setOpen] = useState(false)
  const [copied, setCopied] = useState<"context" | null>(null)

  const prefilledTitle = `Possible issue: ${modelName} on ${benchmarkName} (score ${score})`

  const cardBackendLinks = useMemo(
    () => deriveDatasetLinks(sourceRecordUrl, prefilledTitle),
    [sourceRecordUrl, prefilledTitle],
  )
  const eeeLinks = useMemo(
    () => deriveDatasetLinks(eeeRecordUrl, prefilledTitle),
    [eeeRecordUrl, prefilledTitle],
  )

  // Prefer the EEE upstream as the "correction venue" when known — that's
  // where raw evaluation records live. Fall back to card_backend (the
  // pipeline output) when EEE isn't directly addressable for this row.
  const correctionLinks = eeeLinks ?? cardBackendLinks

  const contextSnippet = [
    `Model: ${modelName} (${modelId})`,
    `Benchmark: ${benchmarkName}${benchmarkId ? ` (${benchmarkId})` : ""}`,
    `Reported score: ${score}`,
    sourceUrl ? `Original source URL: ${sourceUrl}` : null,
    sourceRecordUrl ? `Pipeline record: ${sourceRecordUrl}` : null,
    eeeRecordUrl ? `EEE upstream record: ${eeeRecordUrl}` : null,
  ]
    .filter(Boolean)
    .join("\n")

  const handleCopyContext = async () => {
    try {
      await navigator.clipboard.writeText(contextSnippet)
      setCopied("context")
      setTimeout(() => setCopied(null), 1800)
    } catch {
      // Clipboard might be blocked; ignore — the user can still select text manually.
    }
  }

  return (
    <>
      <Button
        type="button"
        size="sm"
        variant="outline"
        className="h-7 gap-1.5 text-xs"
        onClick={() => setOpen(true)}
      >
        <Flag className="h-3 w-3" />
        Flag this score
      </Button>

      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent className="sm:max-w-lg">
          <DialogHeader>
            <DialogTitle>Flag this score</DialogTitle>
            <DialogDescription>
              Take this report directly to the dataset where the record lives. You can file a
              discussion or open a correction PR against the actual file.
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-4">
            <div className="rounded-xl border bg-muted/20 p-3 text-xs">
              <div className="text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
                Flagging
              </div>
              <div className="mt-1">
                <span className="font-medium">{modelName}</span>{" "}
                <span className="text-muted-foreground">on</span>{" "}
                <span className="font-medium">{benchmarkName}</span>
              </div>
              <div className="text-muted-foreground tabular-nums">Score: {score}</div>
            </div>

            <div className="space-y-2">
              {correctionLinks?.recordViewUrl && (
                <a
                  href={correctionLinks.recordViewUrl}
                  target="_blank"
                  rel="noreferrer"
                  className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
                >
                  <div className="flex items-center gap-2">
                    <FileSearch className="h-4 w-4 text-muted-foreground" />
                    <div>
                      <div className="font-semibold">View the underlying record</div>
                      <div className="text-xs text-muted-foreground">
                        Opens the JSON file on{" "}
                        <span className="font-mono">{correctionLinks.repoSlug}</span>
                      </div>
                    </div>
                  </div>
                  <ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
                </a>
              )}

              {correctionLinks && (
                <a
                  href={correctionLinks.newDiscussionUrl}
                  target="_blank"
                  rel="noreferrer"
                  className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
                >
                  <div className="flex items-center gap-2">
                    <MessageSquare className="h-4 w-4 text-muted-foreground" />
                    <div>
                      <div className="font-semibold">Open a discussion</div>
                      <div className="text-xs text-muted-foreground">
                        Pre-filled title; paste the context snippet into the body.
                      </div>
                    </div>
                  </div>
                  <ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
                </a>
              )}

              {correctionLinks && (
                <a
                  href={correctionLinks.discussionsUrl}
                  target="_blank"
                  rel="noreferrer"
                  className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
                >
                  <div className="flex items-center gap-2">
                    <GitPullRequestArrow className="h-4 w-4 text-muted-foreground" />
                    <div>
                      <div className="font-semibold">Browse existing discussions</div>
                      <div className="text-xs text-muted-foreground">
                        Check whether someone already filed a similar correction.
                      </div>
                    </div>
                  </div>
                  <ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
                </a>
              )}

              {!correctionLinks && (
                <div className="rounded-xl border border-dashed border-amber-300/60 bg-amber-50/40 px-3 py-2.5 text-xs text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/20 dark:text-amber-200">
                  No upstream record URL is recorded for this row, so we can't link directly to
                  the dataset. Copy the context below and file an issue at{" "}
                  <a
                    className="underline-offset-4 hover:underline"
                    href="https://huggingface.co/datasets/evaleval/EEE_datastore/discussions"
                    target="_blank"
                    rel="noreferrer"
                  >
                    evaleval/EEE_datastore/discussions
                  </a>
                  .
                </div>
              )}
            </div>

            <div className="space-y-1.5">
              <div className="flex items-center justify-between">
                <label className="text-xs font-semibold text-muted-foreground">
                  Context to paste into the issue
                </label>
                <Button
                  type="button"
                  size="sm"
                  variant="ghost"
                  onClick={handleCopyContext}
                  className="h-7 gap-1.5 text-xs"
                >
                  <Copy className="h-3 w-3" />
                  {copied === "context" ? "Copied" : "Copy"}
                </Button>
              </div>
              <pre className="max-h-40 overflow-auto whitespace-pre-wrap break-all rounded-xl border bg-muted/10 p-3 text-[11px] leading-5 text-muted-foreground">
                {contextSnippet}
              </pre>
            </div>

            <div className="flex justify-end pt-1">
              <Button type="button" variant="ghost" onClick={() => setOpen(false)}>
                Close
              </Button>
            </div>
          </div>
        </DialogContent>
      </Dialog>
    </>
  )
}