pxengine 0.1.113 → 0.1.115

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.mjs CHANGED
@@ -15793,6 +15793,22 @@ var ShareIcon = () => /* @__PURE__ */ jsxs108("svg", { width: "14", height: "14"
15793
15793
  /* @__PURE__ */ jsx147("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
15794
15794
  ] });
15795
15795
  var CheckIcon = () => /* @__PURE__ */ jsx147("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx147("polyline", { points: "20 6 9 17 4 12" }) });
15796
+ var RefreshIcon = () => /* @__PURE__ */ jsxs108("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15797
+ /* @__PURE__ */ jsx147("polyline", { points: "23 4 23 10 17 10" }),
15798
+ /* @__PURE__ */ jsx147("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
15799
+ ] });
15800
+ var SlideTypeIcon = () => /* @__PURE__ */ jsxs108("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15801
+ /* @__PURE__ */ jsx147("rect", { x: "3", y: "4", width: "18", height: "14", rx: "2" }),
15802
+ /* @__PURE__ */ jsx147("line", { x1: "3", y1: "10", x2: "21", y2: "10" })
15803
+ ] });
15804
+ function formatTemplateLabel(templateId) {
15805
+ if (!templateId) return null;
15806
+ return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
15807
+ }
15808
+ function deriveJobStatusUrl(newJobId, pollUrl, regenerateUrl) {
15809
+ const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/presentations\/[^/]+\/regenerate.*$/, "") ?? "";
15810
+ return `${base}/api/jobs/${newJobId}/status`;
15811
+ }
15796
15812
  var FORMATS = [
15797
15813
  {
15798
15814
  key: "pptx_url",
@@ -15998,11 +16014,20 @@ var PresentationJobCard = ({
15998
16014
  status: initialStatus,
15999
16015
  slide_count: initialSlideCount,
16000
16016
  formats: initialFormats = {},
16017
+ generation_mode: initialGenerationMode,
16018
+ template_id: initialTemplateId,
16019
+ template_version_id: initialTemplateVersionId,
16020
+ review_status: initialReviewStatus,
16021
+ outline: initialOutline,
16001
16022
  error: initialError,
16002
16023
  progress: initialProgress,
16003
16024
  pollUrl,
16004
16025
  authToken,
16005
16026
  shareUrl,
16027
+ approveUrl,
16028
+ approveOutlineUrl,
16029
+ regenerateUrl,
16030
+ hideApproval = false,
16006
16031
  className,
16007
16032
  theme,
16008
16033
  onComplete,
@@ -16016,9 +16041,23 @@ var PresentationJobCard = ({
16016
16041
  const [formats, setFormats] = useState10(initialFormats);
16017
16042
  const [error, setError] = useState10(initialError);
16018
16043
  const [progress, setProgress] = useState10(initialProgress);
16044
+ const [generationMode, setGenerationMode] = useState10(
16045
+ initialGenerationMode || (initialFormats?.html_url ? "template" : "")
16046
+ );
16047
+ const [templateId, setTemplateId] = useState10(initialTemplateId || "");
16048
+ const [, setTemplateVersionId] = useState10(initialTemplateVersionId || "");
16049
+ const [reviewStatus, setReviewStatus] = useState10(initialReviewStatus || "");
16050
+ const [outline, setOutline] = useState10(initialOutline);
16019
16051
  const [showExport, setShowExport] = useState10(false);
16020
16052
  const [showFullscreen, setShowFullscreen] = useState10(false);
16021
16053
  const [copied, setCopied] = useState10(false);
16054
+ const [approving, setApproving] = useState10(false);
16055
+ const [regenerating, setRegenerating] = useState10(false);
16056
+ const [approveError, setApproveError] = useState10(null);
16057
+ const [messageEdits, setMessageEdits] = useState10({});
16058
+ const [approvingOutline, setApprovingOutline] = useState10(false);
16059
+ const [outlineWritePollUrl, setOutlineWritePollUrl] = useState10(null);
16060
+ const [regenPollUrl, setRegenPollUrl] = useState10(null);
16022
16061
  const [currentSlide, setCurrentSlide] = useState10(1);
16023
16062
  const [previewScale, setPreviewScale] = useState10(1);
16024
16063
  const [iframeReady, setIframeReady] = useState10(false);
@@ -16045,6 +16084,22 @@ var PresentationJobCard = ({
16045
16084
  useEffect7(() => {
16046
16085
  if (initialTitle) setTitle(initialTitle);
16047
16086
  }, [initialTitle]);
16087
+ useEffect7(() => {
16088
+ if (initialGenerationMode) setGenerationMode(initialGenerationMode);
16089
+ }, [initialGenerationMode]);
16090
+ useEffect7(() => {
16091
+ if (initialTemplateId) setTemplateId(initialTemplateId);
16092
+ }, [initialTemplateId]);
16093
+ useEffect7(() => {
16094
+ if (initialTemplateVersionId) setTemplateVersionId(initialTemplateVersionId);
16095
+ }, [initialTemplateVersionId]);
16096
+ useEffect7(() => {
16097
+ if (initialReviewStatus) setReviewStatus(initialReviewStatus);
16098
+ }, [initialReviewStatus]);
16099
+ const initialOutlineSlideCount = initialOutline?.slides?.length;
16100
+ useEffect7(() => {
16101
+ if (initialOutline) setOutline(initialOutline);
16102
+ }, [initialOutlineSlideCount]);
16048
16103
  const updateScale = useCallback4(() => {
16049
16104
  if (previewRef.current) setPreviewScale(previewRef.current.offsetWidth / 1280);
16050
16105
  }, []);
@@ -16108,13 +16163,21 @@ var PresentationJobCard = ({
16108
16163
  setProgress(data.progress);
16109
16164
  }
16110
16165
  if (newStatus === "complete" && data.output) {
16111
- const newTitle = data.output.title || initialTitle;
16112
- const newSlideCount = data.output.slide_count || 0;
16113
- const newFormats = data.output.formats || {};
16166
+ const output = data.output;
16167
+ const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "presentation-outline";
16168
+ const newTitle = output.title || initialTitle;
16169
+ const newSlideCount = output.slide_count || 0;
16170
+ const newFormats = output.formats || {};
16114
16171
  setTitle(newTitle);
16115
16172
  setSlideCount(newSlideCount);
16116
16173
  setFormats(newFormats);
16117
- if (!hasNotifiedRef.current && onCompleteRef.current) {
16174
+ if (output.generation_mode) setGenerationMode(output.generation_mode);
16175
+ if (output.template_id) setTemplateId(output.template_id);
16176
+ if (output.template_version_id) setTemplateVersionId(output.template_version_id);
16177
+ if (output.review_status) setReviewStatus(output.review_status);
16178
+ if (output.outline) setOutline(output.outline);
16179
+ else if (output.deck_plan) setOutline(output.deck_plan);
16180
+ if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
16118
16181
  hasNotifiedRef.current = true;
16119
16182
  onCompleteRef.current({
16120
16183
  title: newTitle,
@@ -16134,6 +16197,184 @@ var PresentationJobCard = ({
16134
16197
  }
16135
16198
  // Transient fetch errors are ignored — the shared loop retries next tick.
16136
16199
  );
16200
+ const applyDeckOutput = useCallback4((out, opts) => {
16201
+ if (out.title) setTitle(out.title);
16202
+ if (out.slide_count !== void 0) setSlideCount(out.slide_count);
16203
+ if (out.formats) setFormats(out.formats);
16204
+ if (out.generation_mode) setGenerationMode(out.generation_mode);
16205
+ if (out.template_id) setTemplateId(out.template_id);
16206
+ setReviewStatus(out.review_status || "pending_review");
16207
+ setTemplateVersionId(opts?.regenerated ? "" : out.template_version_id || "");
16208
+ if (!opts?.regenerated && !hasNotifiedRef.current && onCompleteRef.current) {
16209
+ hasNotifiedRef.current = true;
16210
+ onCompleteRef.current({
16211
+ title: out.title ?? title,
16212
+ slide_count: out.slide_count ?? slideCount,
16213
+ formats: out.formats ?? formats
16214
+ });
16215
+ }
16216
+ }, [title, slideCount, formats]);
16217
+ const refetchSourceAndApply = useCallback4(async () => {
16218
+ if (!pollUrl) return;
16219
+ try {
16220
+ const headers = {};
16221
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16222
+ const res = await fetch(pollUrl, { headers });
16223
+ if (!res.ok) return;
16224
+ const data = await res.json();
16225
+ if (data.output) applyDeckOutput(data.output);
16226
+ } catch {
16227
+ }
16228
+ }, [pollUrl, authToken, applyDeckOutput]);
16229
+ useSharedPoll(
16230
+ {
16231
+ key: outlineWritePollUrl,
16232
+ intervalMs: 3e3,
16233
+ fetcher: async () => {
16234
+ const headers = {};
16235
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16236
+ const res = await fetch(outlineWritePollUrl, { headers });
16237
+ if (!res.ok) throw new Error(`poll ${res.status}`);
16238
+ return res.json();
16239
+ },
16240
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
16241
+ },
16242
+ (data) => {
16243
+ if (data.status === "complete") {
16244
+ setOutlineWritePollUrl(null);
16245
+ setApprovingOutline(false);
16246
+ void refetchSourceAndApply();
16247
+ } else if (data.status === "failed") {
16248
+ setApproveError(data.error || "Could not build the presentation");
16249
+ setOutlineWritePollUrl(null);
16250
+ setApprovingOutline(false);
16251
+ }
16252
+ }
16253
+ );
16254
+ useSharedPoll(
16255
+ {
16256
+ key: regenPollUrl,
16257
+ intervalMs: 3e3,
16258
+ fetcher: async () => {
16259
+ const headers = {};
16260
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16261
+ const res = await fetch(regenPollUrl, { headers });
16262
+ if (!res.ok) throw new Error(`poll ${res.status}`);
16263
+ return res.json();
16264
+ },
16265
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
16266
+ },
16267
+ (data) => {
16268
+ if (data.status === "complete") {
16269
+ if (data.output) applyDeckOutput(data.output, { regenerated: true });
16270
+ setRegenPollUrl(null);
16271
+ setRegenerating(false);
16272
+ } else if (data.status === "failed") {
16273
+ setApproveError(data.error || "Could not switch template");
16274
+ setRegenPollUrl(null);
16275
+ setRegenerating(false);
16276
+ }
16277
+ }
16278
+ );
16279
+ const buildEditedOutline = () => {
16280
+ if (!outline || !Array.isArray(outline.slides)) return void 0;
16281
+ let changed = false;
16282
+ const slides = outline.slides.map((s, i) => {
16283
+ const edited = messageEdits[i];
16284
+ if (edited !== void 0 && edited !== s.message) {
16285
+ changed = true;
16286
+ return { ...s, message: edited };
16287
+ }
16288
+ return s;
16289
+ });
16290
+ return changed ? { ...outline, slides } : void 0;
16291
+ };
16292
+ const handleApproveOutline = async () => {
16293
+ if (!_job_id || approvingOutline) return;
16294
+ const endpoint = approveOutlineUrl || `/api/presentations/${_job_id}/approve-outline`;
16295
+ setApprovingOutline(true);
16296
+ setApproveError(null);
16297
+ try {
16298
+ const headers = { "Content-Type": "application/json" };
16299
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16300
+ const editedOutline = buildEditedOutline();
16301
+ const res = await fetch(endpoint, {
16302
+ method: "POST",
16303
+ headers,
16304
+ body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
16305
+ });
16306
+ if (!res.ok) {
16307
+ const err = await res.json().catch(() => ({}));
16308
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
16309
+ }
16310
+ const data = await res.json();
16311
+ if (typeof data.job_id === "string" && data.job_id !== _job_id) {
16312
+ setOutlineWritePollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
16313
+ return;
16314
+ }
16315
+ setApprovingOutline(false);
16316
+ void refetchSourceAndApply();
16317
+ } catch (e) {
16318
+ setApproveError(e instanceof Error ? e.message : "Could not build the presentation");
16319
+ setApprovingOutline(false);
16320
+ }
16321
+ };
16322
+ const handleApprove = async () => {
16323
+ if (!_job_id || approving) return;
16324
+ const endpoint = approveUrl || `/api/presentations/${_job_id}/approve-standard`;
16325
+ setApproving(true);
16326
+ setApproveError(null);
16327
+ try {
16328
+ const headers = { "Content-Type": "application/json" };
16329
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16330
+ const res = await fetch(endpoint, {
16331
+ method: "POST",
16332
+ headers,
16333
+ body: JSON.stringify({ label: title })
16334
+ });
16335
+ if (!res.ok) {
16336
+ const err = await res.json().catch(() => ({}));
16337
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
16338
+ }
16339
+ const data = await res.json();
16340
+ setReviewStatus(data.review_status || "approved");
16341
+ if (data.template_version_id) setTemplateVersionId(data.template_version_id);
16342
+ if (data.template_id) setTemplateId(data.template_id);
16343
+ } catch (e) {
16344
+ setApproveError(e instanceof Error ? e.message : "Could not save the format");
16345
+ } finally {
16346
+ setApproving(false);
16347
+ }
16348
+ };
16349
+ const handleRegenerate = async () => {
16350
+ if (!_job_id || regenerating) return;
16351
+ const endpoint = regenerateUrl || `/api/presentations/${_job_id}/regenerate`;
16352
+ setRegenerating(true);
16353
+ setApproveError(null);
16354
+ try {
16355
+ const headers = { "Content-Type": "application/json" };
16356
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16357
+ const res = await fetch(endpoint, {
16358
+ method: "POST",
16359
+ headers,
16360
+ body: JSON.stringify({})
16361
+ });
16362
+ if (!res.ok) {
16363
+ const err = await res.json().catch(() => ({}));
16364
+ throw new Error(err.detail || err.error || `Regenerate failed (${res.status})`);
16365
+ }
16366
+ const data = await res.json();
16367
+ if (typeof data.job_id === "string" && data.job_id !== _job_id) {
16368
+ setRegenPollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
16369
+ return;
16370
+ }
16371
+ applyDeckOutput(data.output || data, { regenerated: true });
16372
+ setRegenerating(false);
16373
+ } catch (e) {
16374
+ setApproveError(e instanceof Error ? e.message : "Could not switch template");
16375
+ setRegenerating(false);
16376
+ }
16377
+ };
16137
16378
  const handleShare = async () => {
16138
16379
  const link = shareUrl ?? (typeof window !== "undefined" && _job_id ? `${window.location.origin}/p/${_job_id}` : formats.html_url);
16139
16380
  if (!link) return;
@@ -16254,8 +16495,85 @@ var PresentationJobCard = ({
16254
16495
  ] })
16255
16496
  ] }) }) });
16256
16497
  }
16498
+ if (status === "complete" && reviewStatus === "pending_outline_approval") {
16499
+ const slides = Array.isArray(outline?.slides) ? outline.slides : [];
16500
+ const building = Boolean(outlineWritePollUrl) || approvingOutline;
16501
+ return /* @__PURE__ */ jsx147("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs108("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
16502
+ /* @__PURE__ */ jsx147(
16503
+ "div",
16504
+ {
16505
+ className: "h-[3px]",
16506
+ style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
16507
+ }
16508
+ ),
16509
+ /* @__PURE__ */ jsxs108("div", { className: "p-5", children: [
16510
+ /* @__PURE__ */ jsxs108("div", { className: "flex items-start gap-3", children: [
16511
+ /* @__PURE__ */ jsx147(
16512
+ "div",
16513
+ {
16514
+ className: "w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center flex-shrink-0 text-indigo-400",
16515
+ style: { ...t.accentSubtle(0.1), ...t.accentBorder, ...t.accent },
16516
+ children: /* @__PURE__ */ jsx147(SlidesIcon, {})
16517
+ }
16518
+ ),
16519
+ /* @__PURE__ */ jsxs108("div", { className: "flex-1 min-w-0", children: [
16520
+ /* @__PURE__ */ jsx147("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title || "Presentation outline" }),
16521
+ /* @__PURE__ */ jsxs108("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
16522
+ "Review the slide outline",
16523
+ slides.length > 0 ? " \u2014 edit any slide message" : "",
16524
+ ", then build the deck."
16525
+ ] })
16526
+ ] })
16527
+ ] }),
16528
+ /* @__PURE__ */ jsxs108("div", { className: "mt-4 space-y-2", children: [
16529
+ slides.map((s, i) => /* @__PURE__ */ jsxs108("div", { className: "flex items-center gap-2", children: [
16530
+ /* @__PURE__ */ jsx147("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: s.n ?? i + 1 }),
16531
+ /* @__PURE__ */ jsxs108(
16532
+ "span",
16533
+ {
16534
+ className: "flex items-center gap-1 text-[10px] uppercase tracking-wide text-zinc-500 w-20 flex-shrink-0 truncate",
16535
+ title: s.type,
16536
+ children: [
16537
+ /* @__PURE__ */ jsx147(SlideTypeIcon, {}),
16538
+ s.type || "slide"
16539
+ ]
16540
+ }
16541
+ ),
16542
+ /* @__PURE__ */ jsx147(
16543
+ "input",
16544
+ {
16545
+ value: messageEdits[i] ?? s.message ?? "",
16546
+ onChange: (e) => setMessageEdits((prev) => ({ ...prev, [i]: e.target.value })),
16547
+ disabled: building,
16548
+ className: "flex-1 min-w-0 bg-zinc-800/60 border border-zinc-700 rounded-lg px-3 h-8 text-xs text-zinc-200 focus:border-indigo-500 outline-none disabled:opacity-60"
16549
+ }
16550
+ )
16551
+ ] }, i)),
16552
+ slides.length === 0 && /* @__PURE__ */ jsx147("p", { className: "text-xs text-zinc-500", children: "No outline slides were returned; you can still build the deck." })
16553
+ ] }),
16554
+ approveError && /* @__PURE__ */ jsx147("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
16555
+ /* @__PURE__ */ jsx147("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs108(
16556
+ "button",
16557
+ {
16558
+ onClick: handleApproveOutline,
16559
+ disabled: building,
16560
+ title: "Approve this outline and build the full deck",
16561
+ className: "flex items-center gap-1.5 px-3 h-8 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
16562
+ children: [
16563
+ /* @__PURE__ */ jsx147(CheckIcon, {}),
16564
+ /* @__PURE__ */ jsx147("span", { children: building ? "Building deck..." : "Approve & Build" })
16565
+ ]
16566
+ }
16567
+ ) })
16568
+ ] })
16569
+ ] }) });
16570
+ }
16257
16571
  const total = slideCount || 1;
16258
16572
  const hasHtml = Boolean(formats.html_url);
16573
+ const templateLabel = formatTemplateLabel(templateId);
16574
+ const isApproved = reviewStatus === "approved";
16575
+ const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(_job_id);
16576
+ const canRegenerate = canApprove;
16259
16577
  return /* @__PURE__ */ jsxs108(Fragment5, { children: [
16260
16578
  /* @__PURE__ */ jsx147("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs108("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
16261
16579
  /* @__PURE__ */ jsx147("div", { className: "h-[3px]", style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" } }),
@@ -16273,10 +16591,39 @@ var PresentationJobCard = ({
16273
16591
  /* @__PURE__ */ jsx147("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title }),
16274
16592
  /* @__PURE__ */ jsxs108("p", { className: "text-xs text-zinc-500 mt-0.5", style: t.muted, children: [
16275
16593
  slideCount ? `${slideCount} slides` : "Presentation",
16276
- " \xB7 Ready to export"
16277
- ] })
16594
+ " \xB7 Ready to export",
16595
+ templateLabel && ` \xB7 ${templateLabel}`
16596
+ ] }),
16597
+ generationMode === "template" && /* @__PURE__ */ jsx147("p", { className: "text-xs mt-1", children: isApproved ? /* @__PURE__ */ jsx147("span", { className: "text-emerald-400/90", children: "Approved \u2014 future decks use this format" }) : reviewStatus === "pending_review" ? /* @__PURE__ */ jsx147("span", { className: "text-amber-400/90", children: "Review this deck, then approve to lock the format" }) : null })
16278
16598
  ] }),
16279
- /* @__PURE__ */ jsxs108("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
16599
+ /* @__PURE__ */ jsxs108("div", { className: "flex items-center gap-1.5 flex-shrink-0 flex-wrap justify-end", children: [
16600
+ canRegenerate && /* @__PURE__ */ jsxs108(
16601
+ "button",
16602
+ {
16603
+ onClick: handleRegenerate,
16604
+ disabled: regenerating || approving,
16605
+ title: "Try another design template with the same content",
16606
+ className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-300 text-xs font-medium transition-colors disabled:opacity-50",
16607
+ children: [
16608
+ /* @__PURE__ */ jsx147(RefreshIcon, {}),
16609
+ /* @__PURE__ */ jsx147("span", { children: regenerating ? "Switching..." : "Regenerate" })
16610
+ ]
16611
+ }
16612
+ ),
16613
+ canApprove && /* @__PURE__ */ jsxs108(
16614
+ "button",
16615
+ {
16616
+ onClick: handleApprove,
16617
+ disabled: approving,
16618
+ title: "Approve and save this deck format for future runs",
16619
+ className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
16620
+ children: [
16621
+ /* @__PURE__ */ jsx147(CheckIcon, {}),
16622
+ /* @__PURE__ */ jsx147("span", { children: approving ? "Saving..." : "Approve" })
16623
+ ]
16624
+ }
16625
+ ),
16626
+ isApproved && /* @__PURE__ */ jsx147("span", { className: "flex items-center justify-center w-7 h-7 rounded-lg border border-emerald-500/40 bg-emerald-500/10 text-emerald-400", title: "Format saved", children: /* @__PURE__ */ jsx147(CheckIcon, {}) }),
16280
16627
  /* @__PURE__ */ jsx147(
16281
16628
  "button",
16282
16629
  {
@@ -16318,6 +16665,7 @@ var PresentationJobCard = ({
16318
16665
  )
16319
16666
  ] })
16320
16667
  ] }),
16668
+ approveError && /* @__PURE__ */ jsx147("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
16321
16669
  hasHtml && /* @__PURE__ */ jsxs108("div", { className: "mt-4", children: [
16322
16670
  /* @__PURE__ */ jsxs108(
16323
16671
  "div",
@@ -16452,11 +16800,11 @@ var ShareIcon2 = () => /* @__PURE__ */ jsxs109("svg", { width: "14", height: "14
16452
16800
  /* @__PURE__ */ jsx148("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
16453
16801
  ] });
16454
16802
  var CheckIcon2 = () => /* @__PURE__ */ jsx148("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx148("polyline", { points: "20 6 9 17 4 12" }) });
16455
- var RefreshIcon = () => /* @__PURE__ */ jsxs109("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16803
+ var RefreshIcon2 = () => /* @__PURE__ */ jsxs109("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16456
16804
  /* @__PURE__ */ jsx148("polyline", { points: "23 4 23 10 17 10" }),
16457
16805
  /* @__PURE__ */ jsx148("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
16458
16806
  ] });
16459
- function formatTemplateLabel(templateId) {
16807
+ function formatTemplateLabel2(templateId) {
16460
16808
  if (!templateId) return null;
16461
16809
  return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
16462
16810
  }
@@ -16503,7 +16851,7 @@ var FullscreenPreviewModal = ({ url, title, onClose }) => {
16503
16851
  ) })
16504
16852
  ] });
16505
16853
  };
16506
- function deriveJobStatusUrl(newJobId, pollUrl, regenerateUrl) {
16854
+ function deriveJobStatusUrl2(newJobId, pollUrl, regenerateUrl) {
16507
16855
  const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/reports\/[^/]+\/regenerate.*$/, "") ?? "";
16508
16856
  return `${base}/api/jobs/${newJobId}/status`;
16509
16857
  }
@@ -16520,10 +16868,12 @@ var ResearchReportJobCard = (props) => {
16520
16868
  executive_summary: initialSummary,
16521
16869
  key_findings: _initialFindings,
16522
16870
  html_url: initialHtmlUrl,
16871
+ pdf_url: initialPdfUrl,
16523
16872
  generation_mode: initialGenerationMode,
16524
16873
  template_id: initialTemplateId,
16525
16874
  template_version_id: initialTemplateVersionId,
16526
16875
  review_status: initialReviewStatus,
16876
+ outline: initialOutline,
16527
16877
  theme: initialTheme,
16528
16878
  error: initialError,
16529
16879
  progress: initialProgress,
@@ -16531,7 +16881,9 @@ var ResearchReportJobCard = (props) => {
16531
16881
  authToken,
16532
16882
  shareUrl,
16533
16883
  approveUrl,
16884
+ approveOutlineUrl,
16534
16885
  regenerateUrl,
16886
+ hideApproval = false,
16535
16887
  className,
16536
16888
  onComplete,
16537
16889
  onFailed,
@@ -16546,6 +16898,7 @@ var ResearchReportJobCard = (props) => {
16546
16898
  const [wordCount, setWordCount] = useState11(initialWordCount ?? 0);
16547
16899
  const [summary, setSummary] = useState11(initialSummary || "");
16548
16900
  const [htmlUrl, setHtmlUrl] = useState11(initialHtmlUrl || "");
16901
+ const [pdfUrl, setPdfUrl] = useState11(initialPdfUrl || "");
16549
16902
  const [generationMode, setGenerationMode] = useState11(
16550
16903
  initialGenerationMode || (initialHtmlUrl ? "template" : "")
16551
16904
  );
@@ -16554,6 +16907,7 @@ var ResearchReportJobCard = (props) => {
16554
16907
  const [reviewStatus, setReviewStatus] = useState11(
16555
16908
  initialReviewStatus || (initialHtmlUrl ? "pending_review" : "")
16556
16909
  );
16910
+ const [outline, setOutline] = useState11(initialOutline);
16557
16911
  const [theme, setTheme] = useState11(initialTheme || DEFAULT_THEME);
16558
16912
  const [error, setError] = useState11(initialError);
16559
16913
  const [progress, setProgress] = useState11(initialProgress);
@@ -16563,6 +16917,9 @@ var ResearchReportJobCard = (props) => {
16563
16917
  const [approving, setApproving] = useState11(false);
16564
16918
  const [regenerating, setRegenerating] = useState11(false);
16565
16919
  const [approveError, setApproveError] = useState11(null);
16920
+ const [headingEdits, setHeadingEdits] = useState11({});
16921
+ const [approvingOutline, setApprovingOutline] = useState11(false);
16922
+ const [outlineWritePollUrl, setOutlineWritePollUrl] = useState11(null);
16566
16923
  const [regenPollUrl, setRegenPollUrl] = useState11(null);
16567
16924
  const previewRef = useRef7(null);
16568
16925
  const onCompleteRef = useRef7(onComplete);
@@ -16592,6 +16949,10 @@ var ResearchReportJobCard = (props) => {
16592
16949
  useEffect8(() => {
16593
16950
  if (initialReviewStatus) setReviewStatus(initialReviewStatus);
16594
16951
  }, [initialReviewStatus]);
16952
+ const initialOutlineSectionCount = initialOutline?.sections?.length;
16953
+ useEffect8(() => {
16954
+ if (initialOutline) setOutline(initialOutline);
16955
+ }, [initialOutlineSectionCount]);
16595
16956
  useEffect8(() => {
16596
16957
  if (initialDepth) setDepth(initialDepth);
16597
16958
  }, [initialDepth]);
@@ -16657,6 +17018,7 @@ var ResearchReportJobCard = (props) => {
16657
17018
  }
16658
17019
  if (newStatus === "complete" && data.output) {
16659
17020
  const output = data.output;
17021
+ const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "research-report-outline";
16660
17022
  setTitle(output.title || initialTitle);
16661
17023
  setDepth(output.depth || "");
16662
17024
  setSectionCount(output.section_count || 0);
@@ -16664,14 +17026,16 @@ var ResearchReportJobCard = (props) => {
16664
17026
  setWordCount(output.word_count || 0);
16665
17027
  setSummary(output.executive_summary || "");
16666
17028
  setHtmlUrl(output.html_url || "");
17029
+ setPdfUrl(output.pdf_url || "");
16667
17030
  if (output.generation_mode) setGenerationMode(output.generation_mode);
16668
17031
  if (output.template_id) setTemplateId(output.template_id);
16669
17032
  if (output.template_version_id) setTemplateVersionId(output.template_version_id);
16670
17033
  if (output.review_status) setReviewStatus(output.review_status);
17034
+ if (output.outline) setOutline(output.outline);
16671
17035
  if (output.theme) {
16672
17036
  setTheme(output.theme);
16673
17037
  }
16674
- if (!hasNotifiedRef.current && onCompleteRef.current) {
17038
+ if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
16675
17039
  hasNotifiedRef.current = true;
16676
17040
  onCompleteRef.current(output);
16677
17041
  }
@@ -16690,6 +17054,7 @@ var ResearchReportJobCard = (props) => {
16690
17054
  const applyRegeneratedOutput = useCallback5(
16691
17055
  (out) => {
16692
17056
  if (out.html_url) setHtmlUrl(out.html_url);
17057
+ setPdfUrl(out.pdf_url || "");
16693
17058
  if (out.template_id) setTemplateId(out.template_id);
16694
17059
  setReviewStatus("pending_review");
16695
17060
  setTemplateVersionId("");
@@ -16706,6 +17071,7 @@ var ResearchReportJobCard = (props) => {
16706
17071
  word_count: out.word_count ?? wordCount,
16707
17072
  executive_summary: out.executive_summary ?? summary,
16708
17073
  html_url: out.html_url ?? htmlUrl,
17074
+ pdf_url: out.pdf_url ?? pdfUrl,
16709
17075
  generation_mode: "template",
16710
17076
  template_id: out.template_id ?? templateId,
16711
17077
  template_version_id: void 0,
@@ -16713,7 +17079,7 @@ var ResearchReportJobCard = (props) => {
16713
17079
  theme: out.theme ?? theme
16714
17080
  });
16715
17081
  },
16716
- [title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, templateId, theme]
17082
+ [title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, pdfUrl, templateId, theme]
16717
17083
  );
16718
17084
  useSharedPoll(
16719
17085
  {
@@ -16740,6 +17106,104 @@ var ResearchReportJobCard = (props) => {
16740
17106
  }
16741
17107
  }
16742
17108
  );
17109
+ const refetchSourceAndApply = useCallback5(async () => {
17110
+ if (!pollUrl) return;
17111
+ try {
17112
+ const headers = {};
17113
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
17114
+ const res = await fetch(pollUrl, { headers });
17115
+ if (!res.ok) return;
17116
+ const data = await res.json();
17117
+ const out = data.output;
17118
+ if (!out) return;
17119
+ if (out.title) setTitle(out.title);
17120
+ if (out.depth) setDepth(out.depth);
17121
+ if (out.section_count !== void 0) setSectionCount(out.section_count);
17122
+ if (out.source_count !== void 0) setSourceCount(out.source_count);
17123
+ if (out.word_count !== void 0) setWordCount(out.word_count);
17124
+ if (out.executive_summary) setSummary(out.executive_summary);
17125
+ if (out.html_url) setHtmlUrl(out.html_url);
17126
+ setPdfUrl(out.pdf_url || "");
17127
+ if (out.generation_mode) setGenerationMode(out.generation_mode);
17128
+ if (out.template_id) setTemplateId(out.template_id);
17129
+ setTemplateVersionId(out.template_version_id || "");
17130
+ setReviewStatus(out.review_status || "pending_review");
17131
+ if (out.theme) setTheme(out.theme);
17132
+ if (!hasNotifiedRef.current && onCompleteRef.current) {
17133
+ hasNotifiedRef.current = true;
17134
+ onCompleteRef.current(out);
17135
+ }
17136
+ } catch {
17137
+ }
17138
+ }, [pollUrl, authToken]);
17139
+ useSharedPoll(
17140
+ {
17141
+ key: outlineWritePollUrl,
17142
+ intervalMs: 3e3,
17143
+ fetcher: async () => {
17144
+ const headers = {};
17145
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
17146
+ const res = await fetch(outlineWritePollUrl, { headers });
17147
+ if (!res.ok) throw new Error(`poll ${res.status}`);
17148
+ return res.json();
17149
+ },
17150
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
17151
+ },
17152
+ (data) => {
17153
+ if (data.status === "complete") {
17154
+ setOutlineWritePollUrl(null);
17155
+ setApprovingOutline(false);
17156
+ void refetchSourceAndApply();
17157
+ } else if (data.status === "failed") {
17158
+ setApproveError(data.error || "Could not build the report");
17159
+ setOutlineWritePollUrl(null);
17160
+ setApprovingOutline(false);
17161
+ }
17162
+ }
17163
+ );
17164
+ const buildEditedOutline = () => {
17165
+ if (!outline || !Array.isArray(outline.sections)) return void 0;
17166
+ let changed = false;
17167
+ const sections = outline.sections.map((s, i) => {
17168
+ const edited = headingEdits[i];
17169
+ if (edited !== void 0 && edited !== s.heading) {
17170
+ changed = true;
17171
+ return { ...s, heading: edited };
17172
+ }
17173
+ return s;
17174
+ });
17175
+ return changed ? { ...outline, sections } : void 0;
17176
+ };
17177
+ const handleApproveOutline = async () => {
17178
+ if (!job_id || approvingOutline) return;
17179
+ const endpoint = approveOutlineUrl || `/api/reports/${job_id}/approve-outline`;
17180
+ setApprovingOutline(true);
17181
+ setApproveError(null);
17182
+ try {
17183
+ const headers = { "Content-Type": "application/json" };
17184
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
17185
+ const editedOutline = buildEditedOutline();
17186
+ const res = await fetch(endpoint, {
17187
+ method: "POST",
17188
+ headers,
17189
+ body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
17190
+ });
17191
+ if (!res.ok) {
17192
+ const err = await res.json().catch(() => ({}));
17193
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
17194
+ }
17195
+ const data = await res.json();
17196
+ if (typeof data.job_id === "string" && data.job_id !== job_id) {
17197
+ setOutlineWritePollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
17198
+ return;
17199
+ }
17200
+ setApprovingOutline(false);
17201
+ void refetchSourceAndApply();
17202
+ } catch (e) {
17203
+ setApproveError(e instanceof Error ? e.message : "Could not build the report");
17204
+ setApprovingOutline(false);
17205
+ }
17206
+ };
16743
17207
  const formatWordCount = (count) => {
16744
17208
  if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
16745
17209
  return count.toString();
@@ -16845,6 +17309,61 @@ var ResearchReportJobCard = (props) => {
16845
17309
  ] })
16846
17310
  ] }) }) });
16847
17311
  }
17312
+ if (status === "complete" && reviewStatus === "pending_outline_approval") {
17313
+ const sections = Array.isArray(outline?.sections) ? outline.sections : [];
17314
+ const building = Boolean(outlineWritePollUrl) || approvingOutline;
17315
+ return /* @__PURE__ */ jsx148("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs109("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
17316
+ /* @__PURE__ */ jsx148(
17317
+ "div",
17318
+ {
17319
+ className: "h-[3px]",
17320
+ style: { background: "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
17321
+ }
17322
+ ),
17323
+ /* @__PURE__ */ jsxs109("div", { className: "p-5", children: [
17324
+ /* @__PURE__ */ jsxs109("div", { className: "flex items-start gap-3", children: [
17325
+ /* @__PURE__ */ jsx148("div", { className: "w-10 h-10 rounded-xl bg-violet-500/10 border border-violet-500/20 flex items-center justify-center flex-shrink-0 text-violet-300", children: /* @__PURE__ */ jsx148(DocumentIcon, {}) }),
17326
+ /* @__PURE__ */ jsxs109("div", { className: "flex-1 min-w-0", children: [
17327
+ /* @__PURE__ */ jsx148("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", children: title || "Report outline" }),
17328
+ /* @__PURE__ */ jsxs109("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
17329
+ "Review the outline",
17330
+ sections.length > 0 ? " \u2014 edit any heading" : "",
17331
+ ", then build the full report."
17332
+ ] })
17333
+ ] })
17334
+ ] }),
17335
+ /* @__PURE__ */ jsxs109("div", { className: "mt-4 space-y-2", children: [
17336
+ sections.map((s, i) => /* @__PURE__ */ jsxs109("div", { className: "flex items-center gap-2", children: [
17337
+ /* @__PURE__ */ jsx148("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: i + 1 }),
17338
+ /* @__PURE__ */ jsx148(
17339
+ "input",
17340
+ {
17341
+ value: headingEdits[i] ?? s.heading ?? "",
17342
+ onChange: (e) => setHeadingEdits((prev) => ({ ...prev, [i]: e.target.value })),
17343
+ disabled: building,
17344
+ className: "flex-1 min-w-0 bg-zinc-800/60 border border-zinc-700 rounded-lg px-3 h-8 text-xs text-zinc-200 focus:border-violet-500 outline-none disabled:opacity-60"
17345
+ }
17346
+ )
17347
+ ] }, i)),
17348
+ sections.length === 0 && /* @__PURE__ */ jsx148("p", { className: "text-xs text-zinc-500", children: "No outline sections were returned; you can still build the report." })
17349
+ ] }),
17350
+ approveError && /* @__PURE__ */ jsx148("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
17351
+ /* @__PURE__ */ jsx148("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs109(
17352
+ "button",
17353
+ {
17354
+ onClick: handleApproveOutline,
17355
+ disabled: building,
17356
+ title: "Approve this outline and build the full report",
17357
+ className: "flex items-center gap-1.5 px-3 h-8 rounded-lg border border-emerald-700/50 bg-emerald-950/40 hover:border-emerald-500 text-emerald-300 text-xs font-medium transition-colors disabled:opacity-50",
17358
+ children: [
17359
+ /* @__PURE__ */ jsx148(CheckIcon2, {}),
17360
+ /* @__PURE__ */ jsx148("span", { children: building ? "Building report..." : "Approve & Build" })
17361
+ ]
17362
+ }
17363
+ ) })
17364
+ ] })
17365
+ ] }) });
17366
+ }
16848
17367
  if (status === "complete" && !hasHTML) {
16849
17368
  return /* @__PURE__ */ jsx148("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs109("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
16850
17369
  /* @__PURE__ */ jsx148(
@@ -16890,6 +17409,26 @@ var ResearchReportJobCard = (props) => {
16890
17409
  window.open(htmlUrl, "_blank", "noopener,noreferrer");
16891
17410
  }
16892
17411
  };
17412
+ const handleDownloadPdf = async () => {
17413
+ if (!pdfUrl) return;
17414
+ const filename = `${(title ?? "").replace(/[^a-z0-9]/gi, "-").toLowerCase()}.pdf`;
17415
+ const href = `/api/download?url=${encodeURIComponent(pdfUrl)}&filename=${encodeURIComponent(filename)}`;
17416
+ try {
17417
+ const response = await fetch(href);
17418
+ if (!response.ok) throw new Error(`status ${response.status}`);
17419
+ const blob = await response.blob();
17420
+ const objectUrl = window.URL.createObjectURL(blob);
17421
+ const anchor = document.createElement("a");
17422
+ anchor.href = objectUrl;
17423
+ anchor.download = filename;
17424
+ document.body.appendChild(anchor);
17425
+ anchor.click();
17426
+ anchor.remove();
17427
+ window.URL.revokeObjectURL(objectUrl);
17428
+ } catch {
17429
+ window.open(pdfUrl, "_blank", "noopener,noreferrer");
17430
+ }
17431
+ };
16893
17432
  const resolveShareLink = () => {
16894
17433
  if (shareUrl) return shareUrl;
16895
17434
  if (typeof window !== "undefined" && job_id) {
@@ -16908,8 +17447,8 @@ var ResearchReportJobCard = (props) => {
16908
17447
  window.open(link, "_blank", "noopener,noreferrer");
16909
17448
  }
16910
17449
  };
16911
- const canApprove = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
16912
- const canRegenerate = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17450
+ const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17451
+ const canRegenerate = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
16913
17452
  const canShare = Boolean(hasHTML || job_id) && (generationMode !== "template" || reviewStatus !== "pending_review");
16914
17453
  const handleApprove = async () => {
16915
17454
  if (!job_id || approving) return;
@@ -16942,6 +17481,7 @@ var ResearchReportJobCard = (props) => {
16942
17481
  word_count: wordCount,
16943
17482
  executive_summary: summary,
16944
17483
  html_url: htmlUrl,
17484
+ pdf_url: pdfUrl,
16945
17485
  generation_mode: generationMode,
16946
17486
  template_id: data.template_id || templateId,
16947
17487
  template_version_id: data.template_version_id || templateVersionId,
@@ -16974,7 +17514,7 @@ var ResearchReportJobCard = (props) => {
16974
17514
  }
16975
17515
  const data = await res.json();
16976
17516
  if (typeof data.job_id === "string" && data.job_id !== job_id) {
16977
- setRegenPollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
17517
+ setRegenPollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
16978
17518
  return;
16979
17519
  }
16980
17520
  applyRegeneratedOutput(data.output || data);
@@ -16984,7 +17524,7 @@ var ResearchReportJobCard = (props) => {
16984
17524
  setRegenerating(false);
16985
17525
  }
16986
17526
  };
16987
- const templateLabel = formatTemplateLabel(templateId);
17527
+ const templateLabel = formatTemplateLabel2(templateId);
16988
17528
  const isApproved = reviewStatus === "approved";
16989
17529
  return /* @__PURE__ */ jsxs109(Fragment6, { children: [
16990
17530
  /* @__PURE__ */ jsx148("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs109("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
@@ -17033,7 +17573,7 @@ var ResearchReportJobCard = (props) => {
17033
17573
  title: "Try another report template with the same data",
17034
17574
  className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-300 text-xs font-medium transition-colors disabled:opacity-50",
17035
17575
  children: [
17036
- /* @__PURE__ */ jsx148(RefreshIcon, {}),
17576
+ /* @__PURE__ */ jsx148(RefreshIcon2, {}),
17037
17577
  /* @__PURE__ */ jsx148("span", { children: regenerating ? "Switching..." : "Regenerate" })
17038
17578
  ]
17039
17579
  }
@@ -17079,6 +17619,18 @@ var ResearchReportJobCard = (props) => {
17079
17619
  ]
17080
17620
  }
17081
17621
  ),
17622
+ pdfUrl && /* @__PURE__ */ jsxs109(
17623
+ "button",
17624
+ {
17625
+ onClick: handleDownloadPdf,
17626
+ title: "Download PDF",
17627
+ className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-400 hover:text-zinc-200 text-xs font-medium transition-colors",
17628
+ children: [
17629
+ /* @__PURE__ */ jsx148(DownloadIcon2, {}),
17630
+ /* @__PURE__ */ jsx148("span", { children: "PDF" })
17631
+ ]
17632
+ }
17633
+ ),
17082
17634
  /* @__PURE__ */ jsxs109(
17083
17635
  "button",
17084
17636
  {