pxengine 0.1.112 → 0.1.114

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.cjs CHANGED
@@ -16125,6 +16125,22 @@ var ShareIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { w
16125
16125
  /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
16126
16126
  ] });
16127
16127
  var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
16128
+ var RefreshIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16129
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("polyline", { points: "23 4 23 10 17 10" }),
16130
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
16131
+ ] });
16132
+ var SlideTypeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16133
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("rect", { x: "3", y: "4", width: "18", height: "14", rx: "2" }),
16134
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" })
16135
+ ] });
16136
+ function formatTemplateLabel(templateId) {
16137
+ if (!templateId) return null;
16138
+ return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
16139
+ }
16140
+ function deriveJobStatusUrl(newJobId, pollUrl, regenerateUrl) {
16141
+ const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/presentations\/[^/]+\/regenerate.*$/, "") ?? "";
16142
+ return `${base}/api/jobs/${newJobId}/status`;
16143
+ }
16128
16144
  var FORMATS = [
16129
16145
  {
16130
16146
  key: "pptx_url",
@@ -16330,11 +16346,20 @@ var PresentationJobCard = ({
16330
16346
  status: initialStatus,
16331
16347
  slide_count: initialSlideCount,
16332
16348
  formats: initialFormats = {},
16349
+ generation_mode: initialGenerationMode,
16350
+ template_id: initialTemplateId,
16351
+ template_version_id: initialTemplateVersionId,
16352
+ review_status: initialReviewStatus,
16353
+ outline: initialOutline,
16333
16354
  error: initialError,
16334
16355
  progress: initialProgress,
16335
16356
  pollUrl,
16336
16357
  authToken,
16337
16358
  shareUrl,
16359
+ approveUrl,
16360
+ approveOutlineUrl,
16361
+ regenerateUrl,
16362
+ hideApproval = false,
16338
16363
  className,
16339
16364
  theme,
16340
16365
  onComplete,
@@ -16348,9 +16373,23 @@ var PresentationJobCard = ({
16348
16373
  const [formats, setFormats] = (0, import_react78.useState)(initialFormats);
16349
16374
  const [error, setError] = (0, import_react78.useState)(initialError);
16350
16375
  const [progress, setProgress] = (0, import_react78.useState)(initialProgress);
16376
+ const [generationMode, setGenerationMode] = (0, import_react78.useState)(
16377
+ initialGenerationMode || (initialFormats?.html_url ? "template" : "")
16378
+ );
16379
+ const [templateId, setTemplateId] = (0, import_react78.useState)(initialTemplateId || "");
16380
+ const [, setTemplateVersionId] = (0, import_react78.useState)(initialTemplateVersionId || "");
16381
+ const [reviewStatus, setReviewStatus] = (0, import_react78.useState)(initialReviewStatus || "");
16382
+ const [outline, setOutline] = (0, import_react78.useState)(initialOutline);
16351
16383
  const [showExport, setShowExport] = (0, import_react78.useState)(false);
16352
16384
  const [showFullscreen, setShowFullscreen] = (0, import_react78.useState)(false);
16353
16385
  const [copied, setCopied] = (0, import_react78.useState)(false);
16386
+ const [approving, setApproving] = (0, import_react78.useState)(false);
16387
+ const [regenerating, setRegenerating] = (0, import_react78.useState)(false);
16388
+ const [approveError, setApproveError] = (0, import_react78.useState)(null);
16389
+ const [messageEdits, setMessageEdits] = (0, import_react78.useState)({});
16390
+ const [approvingOutline, setApprovingOutline] = (0, import_react78.useState)(false);
16391
+ const [outlineWritePollUrl, setOutlineWritePollUrl] = (0, import_react78.useState)(null);
16392
+ const [regenPollUrl, setRegenPollUrl] = (0, import_react78.useState)(null);
16354
16393
  const [currentSlide, setCurrentSlide] = (0, import_react78.useState)(1);
16355
16394
  const [previewScale, setPreviewScale] = (0, import_react78.useState)(1);
16356
16395
  const [iframeReady, setIframeReady] = (0, import_react78.useState)(false);
@@ -16377,6 +16416,22 @@ var PresentationJobCard = ({
16377
16416
  (0, import_react78.useEffect)(() => {
16378
16417
  if (initialTitle) setTitle(initialTitle);
16379
16418
  }, [initialTitle]);
16419
+ (0, import_react78.useEffect)(() => {
16420
+ if (initialGenerationMode) setGenerationMode(initialGenerationMode);
16421
+ }, [initialGenerationMode]);
16422
+ (0, import_react78.useEffect)(() => {
16423
+ if (initialTemplateId) setTemplateId(initialTemplateId);
16424
+ }, [initialTemplateId]);
16425
+ (0, import_react78.useEffect)(() => {
16426
+ if (initialTemplateVersionId) setTemplateVersionId(initialTemplateVersionId);
16427
+ }, [initialTemplateVersionId]);
16428
+ (0, import_react78.useEffect)(() => {
16429
+ if (initialReviewStatus) setReviewStatus(initialReviewStatus);
16430
+ }, [initialReviewStatus]);
16431
+ const initialOutlineSlideCount = initialOutline?.slides?.length;
16432
+ (0, import_react78.useEffect)(() => {
16433
+ if (initialOutline) setOutline(initialOutline);
16434
+ }, [initialOutlineSlideCount]);
16380
16435
  const updateScale = (0, import_react78.useCallback)(() => {
16381
16436
  if (previewRef.current) setPreviewScale(previewRef.current.offsetWidth / 1280);
16382
16437
  }, []);
@@ -16440,13 +16495,21 @@ var PresentationJobCard = ({
16440
16495
  setProgress(data.progress);
16441
16496
  }
16442
16497
  if (newStatus === "complete" && data.output) {
16443
- const newTitle = data.output.title || initialTitle;
16444
- const newSlideCount = data.output.slide_count || 0;
16445
- const newFormats = data.output.formats || {};
16498
+ const output = data.output;
16499
+ const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "presentation-outline";
16500
+ const newTitle = output.title || initialTitle;
16501
+ const newSlideCount = output.slide_count || 0;
16502
+ const newFormats = output.formats || {};
16446
16503
  setTitle(newTitle);
16447
16504
  setSlideCount(newSlideCount);
16448
16505
  setFormats(newFormats);
16449
- if (!hasNotifiedRef.current && onCompleteRef.current) {
16506
+ if (output.generation_mode) setGenerationMode(output.generation_mode);
16507
+ if (output.template_id) setTemplateId(output.template_id);
16508
+ if (output.template_version_id) setTemplateVersionId(output.template_version_id);
16509
+ if (output.review_status) setReviewStatus(output.review_status);
16510
+ if (output.outline) setOutline(output.outline);
16511
+ else if (output.deck_plan) setOutline(output.deck_plan);
16512
+ if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
16450
16513
  hasNotifiedRef.current = true;
16451
16514
  onCompleteRef.current({
16452
16515
  title: newTitle,
@@ -16466,6 +16529,184 @@ var PresentationJobCard = ({
16466
16529
  }
16467
16530
  // Transient fetch errors are ignored — the shared loop retries next tick.
16468
16531
  );
16532
+ const applyDeckOutput = (0, import_react78.useCallback)((out, opts) => {
16533
+ if (out.title) setTitle(out.title);
16534
+ if (out.slide_count !== void 0) setSlideCount(out.slide_count);
16535
+ if (out.formats) setFormats(out.formats);
16536
+ if (out.generation_mode) setGenerationMode(out.generation_mode);
16537
+ if (out.template_id) setTemplateId(out.template_id);
16538
+ setReviewStatus(out.review_status || "pending_review");
16539
+ setTemplateVersionId(opts?.regenerated ? "" : out.template_version_id || "");
16540
+ if (!opts?.regenerated && !hasNotifiedRef.current && onCompleteRef.current) {
16541
+ hasNotifiedRef.current = true;
16542
+ onCompleteRef.current({
16543
+ title: out.title ?? title,
16544
+ slide_count: out.slide_count ?? slideCount,
16545
+ formats: out.formats ?? formats
16546
+ });
16547
+ }
16548
+ }, [title, slideCount, formats]);
16549
+ const refetchSourceAndApply = (0, import_react78.useCallback)(async () => {
16550
+ if (!pollUrl) return;
16551
+ try {
16552
+ const headers = {};
16553
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16554
+ const res = await fetch(pollUrl, { headers });
16555
+ if (!res.ok) return;
16556
+ const data = await res.json();
16557
+ if (data.output) applyDeckOutput(data.output);
16558
+ } catch {
16559
+ }
16560
+ }, [pollUrl, authToken, applyDeckOutput]);
16561
+ useSharedPoll(
16562
+ {
16563
+ key: outlineWritePollUrl,
16564
+ intervalMs: 3e3,
16565
+ fetcher: async () => {
16566
+ const headers = {};
16567
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16568
+ const res = await fetch(outlineWritePollUrl, { headers });
16569
+ if (!res.ok) throw new Error(`poll ${res.status}`);
16570
+ return res.json();
16571
+ },
16572
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
16573
+ },
16574
+ (data) => {
16575
+ if (data.status === "complete") {
16576
+ setOutlineWritePollUrl(null);
16577
+ setApprovingOutline(false);
16578
+ void refetchSourceAndApply();
16579
+ } else if (data.status === "failed") {
16580
+ setApproveError(data.error || "Could not build the presentation");
16581
+ setOutlineWritePollUrl(null);
16582
+ setApprovingOutline(false);
16583
+ }
16584
+ }
16585
+ );
16586
+ useSharedPoll(
16587
+ {
16588
+ key: regenPollUrl,
16589
+ intervalMs: 3e3,
16590
+ fetcher: async () => {
16591
+ const headers = {};
16592
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
16593
+ const res = await fetch(regenPollUrl, { headers });
16594
+ if (!res.ok) throw new Error(`poll ${res.status}`);
16595
+ return res.json();
16596
+ },
16597
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
16598
+ },
16599
+ (data) => {
16600
+ if (data.status === "complete") {
16601
+ if (data.output) applyDeckOutput(data.output, { regenerated: true });
16602
+ setRegenPollUrl(null);
16603
+ setRegenerating(false);
16604
+ } else if (data.status === "failed") {
16605
+ setApproveError(data.error || "Could not switch template");
16606
+ setRegenPollUrl(null);
16607
+ setRegenerating(false);
16608
+ }
16609
+ }
16610
+ );
16611
+ const buildEditedOutline = () => {
16612
+ if (!outline || !Array.isArray(outline.slides)) return void 0;
16613
+ let changed = false;
16614
+ const slides = outline.slides.map((s, i) => {
16615
+ const edited = messageEdits[i];
16616
+ if (edited !== void 0 && edited !== s.message) {
16617
+ changed = true;
16618
+ return { ...s, message: edited };
16619
+ }
16620
+ return s;
16621
+ });
16622
+ return changed ? { ...outline, slides } : void 0;
16623
+ };
16624
+ const handleApproveOutline = async () => {
16625
+ if (!_job_id || approvingOutline) return;
16626
+ const endpoint = approveOutlineUrl || `/api/presentations/${_job_id}/approve-outline`;
16627
+ setApprovingOutline(true);
16628
+ setApproveError(null);
16629
+ try {
16630
+ const headers = { "Content-Type": "application/json" };
16631
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16632
+ const editedOutline = buildEditedOutline();
16633
+ const res = await fetch(endpoint, {
16634
+ method: "POST",
16635
+ headers,
16636
+ body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
16637
+ });
16638
+ if (!res.ok) {
16639
+ const err = await res.json().catch(() => ({}));
16640
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
16641
+ }
16642
+ const data = await res.json();
16643
+ if (typeof data.job_id === "string" && data.job_id !== _job_id) {
16644
+ setOutlineWritePollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
16645
+ return;
16646
+ }
16647
+ setApprovingOutline(false);
16648
+ void refetchSourceAndApply();
16649
+ } catch (e) {
16650
+ setApproveError(e instanceof Error ? e.message : "Could not build the presentation");
16651
+ setApprovingOutline(false);
16652
+ }
16653
+ };
16654
+ const handleApprove = async () => {
16655
+ if (!_job_id || approving) return;
16656
+ const endpoint = approveUrl || `/api/presentations/${_job_id}/approve-standard`;
16657
+ setApproving(true);
16658
+ setApproveError(null);
16659
+ try {
16660
+ const headers = { "Content-Type": "application/json" };
16661
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16662
+ const res = await fetch(endpoint, {
16663
+ method: "POST",
16664
+ headers,
16665
+ body: JSON.stringify({ label: title })
16666
+ });
16667
+ if (!res.ok) {
16668
+ const err = await res.json().catch(() => ({}));
16669
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
16670
+ }
16671
+ const data = await res.json();
16672
+ setReviewStatus(data.review_status || "approved");
16673
+ if (data.template_version_id) setTemplateVersionId(data.template_version_id);
16674
+ if (data.template_id) setTemplateId(data.template_id);
16675
+ } catch (e) {
16676
+ setApproveError(e instanceof Error ? e.message : "Could not save the format");
16677
+ } finally {
16678
+ setApproving(false);
16679
+ }
16680
+ };
16681
+ const handleRegenerate = async () => {
16682
+ if (!_job_id || regenerating) return;
16683
+ const endpoint = regenerateUrl || `/api/presentations/${_job_id}/regenerate`;
16684
+ setRegenerating(true);
16685
+ setApproveError(null);
16686
+ try {
16687
+ const headers = { "Content-Type": "application/json" };
16688
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
16689
+ const res = await fetch(endpoint, {
16690
+ method: "POST",
16691
+ headers,
16692
+ body: JSON.stringify({})
16693
+ });
16694
+ if (!res.ok) {
16695
+ const err = await res.json().catch(() => ({}));
16696
+ throw new Error(err.detail || err.error || `Regenerate failed (${res.status})`);
16697
+ }
16698
+ const data = await res.json();
16699
+ if (typeof data.job_id === "string" && data.job_id !== _job_id) {
16700
+ setRegenPollUrl(deriveJobStatusUrl(data.job_id, pollUrl, regenerateUrl));
16701
+ return;
16702
+ }
16703
+ applyDeckOutput(data.output || data, { regenerated: true });
16704
+ setRegenerating(false);
16705
+ } catch (e) {
16706
+ setApproveError(e instanceof Error ? e.message : "Could not switch template");
16707
+ setRegenerating(false);
16708
+ }
16709
+ };
16469
16710
  const handleShare = async () => {
16470
16711
  const link = shareUrl ?? (typeof window !== "undefined" && _job_id ? `${window.location.origin}/p/${_job_id}` : formats.html_url);
16471
16712
  if (!link) return;
@@ -16586,8 +16827,85 @@ var PresentationJobCard = ({
16586
16827
  ] })
16587
16828
  ] }) }) });
16588
16829
  }
16830
+ if (status === "complete" && reviewStatus === "pending_outline_approval") {
16831
+ const slides = Array.isArray(outline?.slides) ? outline.slides : [];
16832
+ const building = Boolean(outlineWritePollUrl) || approvingOutline;
16833
+ return /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
16834
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
16835
+ "div",
16836
+ {
16837
+ className: "h-[3px]",
16838
+ style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
16839
+ }
16840
+ ),
16841
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "p-5", children: [
16842
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-start gap-3", children: [
16843
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
16844
+ "div",
16845
+ {
16846
+ 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",
16847
+ style: { ...t.accentSubtle(0.1), ...t.accentBorder, ...t.accent },
16848
+ children: /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(SlidesIcon, {})
16849
+ }
16850
+ ),
16851
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex-1 min-w-0", children: [
16852
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title || "Presentation outline" }),
16853
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
16854
+ "Review the slide outline",
16855
+ slides.length > 0 ? " \u2014 edit any slide message" : "",
16856
+ ", then build the deck."
16857
+ ] })
16858
+ ] })
16859
+ ] }),
16860
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "mt-4 space-y-2", children: [
16861
+ slides.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-2", children: [
16862
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: s.n ?? i + 1 }),
16863
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
16864
+ "span",
16865
+ {
16866
+ className: "flex items-center gap-1 text-[10px] uppercase tracking-wide text-zinc-500 w-20 flex-shrink-0 truncate",
16867
+ title: s.type,
16868
+ children: [
16869
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(SlideTypeIcon, {}),
16870
+ s.type || "slide"
16871
+ ]
16872
+ }
16873
+ ),
16874
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
16875
+ "input",
16876
+ {
16877
+ value: messageEdits[i] ?? s.message ?? "",
16878
+ onChange: (e) => setMessageEdits((prev) => ({ ...prev, [i]: e.target.value })),
16879
+ disabled: building,
16880
+ 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"
16881
+ }
16882
+ )
16883
+ ] }, i)),
16884
+ slides.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "text-xs text-zinc-500", children: "No outline slides were returned; you can still build the deck." })
16885
+ ] }),
16886
+ approveError && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
16887
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
16888
+ "button",
16889
+ {
16890
+ onClick: handleApproveOutline,
16891
+ disabled: building,
16892
+ title: "Approve this outline and build the full deck",
16893
+ 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",
16894
+ children: [
16895
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}),
16896
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: building ? "Building deck..." : "Approve & Build" })
16897
+ ]
16898
+ }
16899
+ ) })
16900
+ ] })
16901
+ ] }) });
16902
+ }
16589
16903
  const total = slideCount || 1;
16590
16904
  const hasHtml = Boolean(formats.html_url);
16905
+ const templateLabel = formatTemplateLabel(templateId);
16906
+ const isApproved = reviewStatus === "approved";
16907
+ const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(_job_id);
16908
+ const canRegenerate = canApprove;
16591
16909
  return /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(import_jsx_runtime147.Fragment, { children: [
16592
16910
  /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", style: t.root, children: [
16593
16911
  /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("div", { className: "h-[3px]", style: { background: accentGradient || "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" } }),
@@ -16605,10 +16923,39 @@ var PresentationJobCard = ({
16605
16923
  /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", style: t.text, children: title }),
16606
16924
  /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("p", { className: "text-xs text-zinc-500 mt-0.5", style: t.muted, children: [
16607
16925
  slideCount ? `${slideCount} slides` : "Presentation",
16608
- " \xB7 Ready to export"
16609
- ] })
16926
+ " \xB7 Ready to export",
16927
+ templateLabel && ` \xB7 ${templateLabel}`
16928
+ ] }),
16929
+ generationMode === "template" && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "text-xs mt-1", children: isApproved ? /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-emerald-400/90", children: "Approved \u2014 future decks use this format" }) : reviewStatus === "pending_review" ? /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { className: "text-amber-400/90", children: "Review this deck, then approve to lock the format" }) : null })
16610
16930
  ] }),
16611
- /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
16931
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0 flex-wrap justify-end", children: [
16932
+ canRegenerate && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
16933
+ "button",
16934
+ {
16935
+ onClick: handleRegenerate,
16936
+ disabled: regenerating || approving,
16937
+ title: "Try another design template with the same content",
16938
+ 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",
16939
+ children: [
16940
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(RefreshIcon, {}),
16941
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
16942
+ ]
16943
+ }
16944
+ ),
16945
+ canApprove && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
16946
+ "button",
16947
+ {
16948
+ onClick: handleApprove,
16949
+ disabled: approving,
16950
+ title: "Approve and save this deck format for future runs",
16951
+ 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",
16952
+ children: [
16953
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}),
16954
+ /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("span", { children: approving ? "Saving..." : "Approve" })
16955
+ ]
16956
+ }
16957
+ ),
16958
+ isApproved && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("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__ */ (0, import_jsx_runtime147.jsx)(CheckIcon, {}) }),
16612
16959
  /* @__PURE__ */ (0, import_jsx_runtime147.jsx)(
16613
16960
  "button",
16614
16961
  {
@@ -16650,6 +16997,7 @@ var PresentationJobCard = ({
16650
16997
  )
16651
16998
  ] })
16652
16999
  ] }),
17000
+ approveError && /* @__PURE__ */ (0, import_jsx_runtime147.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
16653
17001
  hasHtml && /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)("div", { className: "mt-4", children: [
16654
17002
  /* @__PURE__ */ (0, import_jsx_runtime147.jsxs)(
16655
17003
  "div",
@@ -16784,11 +17132,11 @@ var ShareIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("svg", {
16784
17132
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
16785
17133
  ] });
16786
17134
  var CheckIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
16787
- var RefreshIcon = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
17135
+ var RefreshIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16788
17136
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("polyline", { points: "23 4 23 10 17 10" }),
16789
17137
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
16790
17138
  ] });
16791
- function formatTemplateLabel(templateId) {
17139
+ function formatTemplateLabel2(templateId) {
16792
17140
  if (!templateId) return null;
16793
17141
  return templateId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
16794
17142
  }
@@ -16835,6 +17183,10 @@ var FullscreenPreviewModal = ({ url, title, onClose }) => {
16835
17183
  ) })
16836
17184
  ] });
16837
17185
  };
17186
+ function deriveJobStatusUrl2(newJobId, pollUrl, regenerateUrl) {
17187
+ const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/reports\/[^/]+\/regenerate.*$/, "") ?? "";
17188
+ return `${base}/api/jobs/${newJobId}/status`;
17189
+ }
16838
17190
  var ResearchReportJobCard = (props) => {
16839
17191
  const {
16840
17192
  job_id,
@@ -16852,6 +17204,7 @@ var ResearchReportJobCard = (props) => {
16852
17204
  template_id: initialTemplateId,
16853
17205
  template_version_id: initialTemplateVersionId,
16854
17206
  review_status: initialReviewStatus,
17207
+ outline: initialOutline,
16855
17208
  theme: initialTheme,
16856
17209
  error: initialError,
16857
17210
  progress: initialProgress,
@@ -16859,7 +17212,9 @@ var ResearchReportJobCard = (props) => {
16859
17212
  authToken,
16860
17213
  shareUrl,
16861
17214
  approveUrl,
17215
+ approveOutlineUrl,
16862
17216
  regenerateUrl,
17217
+ hideApproval = false,
16863
17218
  className,
16864
17219
  onComplete,
16865
17220
  onFailed,
@@ -16882,6 +17237,7 @@ var ResearchReportJobCard = (props) => {
16882
17237
  const [reviewStatus, setReviewStatus] = (0, import_react79.useState)(
16883
17238
  initialReviewStatus || (initialHtmlUrl ? "pending_review" : "")
16884
17239
  );
17240
+ const [outline, setOutline] = (0, import_react79.useState)(initialOutline);
16885
17241
  const [theme, setTheme] = (0, import_react79.useState)(initialTheme || DEFAULT_THEME);
16886
17242
  const [error, setError] = (0, import_react79.useState)(initialError);
16887
17243
  const [progress, setProgress] = (0, import_react79.useState)(initialProgress);
@@ -16891,6 +17247,10 @@ var ResearchReportJobCard = (props) => {
16891
17247
  const [approving, setApproving] = (0, import_react79.useState)(false);
16892
17248
  const [regenerating, setRegenerating] = (0, import_react79.useState)(false);
16893
17249
  const [approveError, setApproveError] = (0, import_react79.useState)(null);
17250
+ const [headingEdits, setHeadingEdits] = (0, import_react79.useState)({});
17251
+ const [approvingOutline, setApprovingOutline] = (0, import_react79.useState)(false);
17252
+ const [outlineWritePollUrl, setOutlineWritePollUrl] = (0, import_react79.useState)(null);
17253
+ const [regenPollUrl, setRegenPollUrl] = (0, import_react79.useState)(null);
16894
17254
  const previewRef = (0, import_react79.useRef)(null);
16895
17255
  const onCompleteRef = (0, import_react79.useRef)(onComplete);
16896
17256
  const onFailedRef = (0, import_react79.useRef)(onFailed);
@@ -16919,6 +17279,10 @@ var ResearchReportJobCard = (props) => {
16919
17279
  (0, import_react79.useEffect)(() => {
16920
17280
  if (initialReviewStatus) setReviewStatus(initialReviewStatus);
16921
17281
  }, [initialReviewStatus]);
17282
+ const initialOutlineSectionCount = initialOutline?.sections?.length;
17283
+ (0, import_react79.useEffect)(() => {
17284
+ if (initialOutline) setOutline(initialOutline);
17285
+ }, [initialOutlineSectionCount]);
16922
17286
  (0, import_react79.useEffect)(() => {
16923
17287
  if (initialDepth) setDepth(initialDepth);
16924
17288
  }, [initialDepth]);
@@ -16984,6 +17348,7 @@ var ResearchReportJobCard = (props) => {
16984
17348
  }
16985
17349
  if (newStatus === "complete" && data.output) {
16986
17350
  const output = data.output;
17351
+ const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "research-report-outline";
16987
17352
  setTitle(output.title || initialTitle);
16988
17353
  setDepth(output.depth || "");
16989
17354
  setSectionCount(output.section_count || 0);
@@ -16995,10 +17360,11 @@ var ResearchReportJobCard = (props) => {
16995
17360
  if (output.template_id) setTemplateId(output.template_id);
16996
17361
  if (output.template_version_id) setTemplateVersionId(output.template_version_id);
16997
17362
  if (output.review_status) setReviewStatus(output.review_status);
17363
+ if (output.outline) setOutline(output.outline);
16998
17364
  if (output.theme) {
16999
17365
  setTheme(output.theme);
17000
17366
  }
17001
- if (!hasNotifiedRef.current && onCompleteRef.current) {
17367
+ if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
17002
17368
  hasNotifiedRef.current = true;
17003
17369
  onCompleteRef.current(output);
17004
17370
  }
@@ -17014,6 +17380,156 @@ var ResearchReportJobCard = (props) => {
17014
17380
  }
17015
17381
  // Transient fetch errors are ignored — the shared loop retries next tick.
17016
17382
  );
17383
+ const applyRegeneratedOutput = (0, import_react79.useCallback)(
17384
+ (out) => {
17385
+ if (out.html_url) setHtmlUrl(out.html_url);
17386
+ if (out.template_id) setTemplateId(out.template_id);
17387
+ setReviewStatus("pending_review");
17388
+ setTemplateVersionId("");
17389
+ if (out.section_count !== void 0) setSectionCount(out.section_count);
17390
+ if (out.title) setTitle(out.title);
17391
+ if (out.depth) setDepth(out.depth);
17392
+ if (out.theme) setTheme(out.theme);
17393
+ if (!onCompleteRef.current) return;
17394
+ onCompleteRef.current({
17395
+ title: out.title ?? title,
17396
+ depth: out.depth ?? depth,
17397
+ section_count: out.section_count ?? sectionCount,
17398
+ source_count: out.source_count ?? sourceCount,
17399
+ word_count: out.word_count ?? wordCount,
17400
+ executive_summary: out.executive_summary ?? summary,
17401
+ html_url: out.html_url ?? htmlUrl,
17402
+ generation_mode: "template",
17403
+ template_id: out.template_id ?? templateId,
17404
+ template_version_id: void 0,
17405
+ review_status: "pending_review",
17406
+ theme: out.theme ?? theme
17407
+ });
17408
+ },
17409
+ [title, depth, sectionCount, sourceCount, wordCount, summary, htmlUrl, templateId, theme]
17410
+ );
17411
+ useSharedPoll(
17412
+ {
17413
+ key: regenPollUrl,
17414
+ intervalMs: 3e3,
17415
+ fetcher: async () => {
17416
+ const headers = {};
17417
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
17418
+ const res = await fetch(regenPollUrl, { headers });
17419
+ if (!res.ok) throw new Error(`poll ${res.status}`);
17420
+ return res.json();
17421
+ },
17422
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
17423
+ },
17424
+ (data) => {
17425
+ if (data.status === "complete") {
17426
+ if (data.output) applyRegeneratedOutput(data.output);
17427
+ setRegenPollUrl(null);
17428
+ setRegenerating(false);
17429
+ } else if (data.status === "failed") {
17430
+ setApproveError(data.error || "Could not switch template");
17431
+ setRegenPollUrl(null);
17432
+ setRegenerating(false);
17433
+ }
17434
+ }
17435
+ );
17436
+ const refetchSourceAndApply = (0, import_react79.useCallback)(async () => {
17437
+ if (!pollUrl) return;
17438
+ try {
17439
+ const headers = {};
17440
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
17441
+ const res = await fetch(pollUrl, { headers });
17442
+ if (!res.ok) return;
17443
+ const data = await res.json();
17444
+ const out = data.output;
17445
+ if (!out) return;
17446
+ if (out.title) setTitle(out.title);
17447
+ if (out.depth) setDepth(out.depth);
17448
+ if (out.section_count !== void 0) setSectionCount(out.section_count);
17449
+ if (out.source_count !== void 0) setSourceCount(out.source_count);
17450
+ if (out.word_count !== void 0) setWordCount(out.word_count);
17451
+ if (out.executive_summary) setSummary(out.executive_summary);
17452
+ if (out.html_url) setHtmlUrl(out.html_url);
17453
+ if (out.generation_mode) setGenerationMode(out.generation_mode);
17454
+ if (out.template_id) setTemplateId(out.template_id);
17455
+ setTemplateVersionId(out.template_version_id || "");
17456
+ setReviewStatus(out.review_status || "pending_review");
17457
+ if (out.theme) setTheme(out.theme);
17458
+ if (!hasNotifiedRef.current && onCompleteRef.current) {
17459
+ hasNotifiedRef.current = true;
17460
+ onCompleteRef.current(out);
17461
+ }
17462
+ } catch {
17463
+ }
17464
+ }, [pollUrl, authToken]);
17465
+ useSharedPoll(
17466
+ {
17467
+ key: outlineWritePollUrl,
17468
+ intervalMs: 3e3,
17469
+ fetcher: async () => {
17470
+ const headers = {};
17471
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
17472
+ const res = await fetch(outlineWritePollUrl, { headers });
17473
+ if (!res.ok) throw new Error(`poll ${res.status}`);
17474
+ return res.json();
17475
+ },
17476
+ shouldContinue: (data) => data.status !== "complete" && data.status !== "failed"
17477
+ },
17478
+ (data) => {
17479
+ if (data.status === "complete") {
17480
+ setOutlineWritePollUrl(null);
17481
+ setApprovingOutline(false);
17482
+ void refetchSourceAndApply();
17483
+ } else if (data.status === "failed") {
17484
+ setApproveError(data.error || "Could not build the report");
17485
+ setOutlineWritePollUrl(null);
17486
+ setApprovingOutline(false);
17487
+ }
17488
+ }
17489
+ );
17490
+ const buildEditedOutline = () => {
17491
+ if (!outline || !Array.isArray(outline.sections)) return void 0;
17492
+ let changed = false;
17493
+ const sections = outline.sections.map((s, i) => {
17494
+ const edited = headingEdits[i];
17495
+ if (edited !== void 0 && edited !== s.heading) {
17496
+ changed = true;
17497
+ return { ...s, heading: edited };
17498
+ }
17499
+ return s;
17500
+ });
17501
+ return changed ? { ...outline, sections } : void 0;
17502
+ };
17503
+ const handleApproveOutline = async () => {
17504
+ if (!job_id || approvingOutline) return;
17505
+ const endpoint = approveOutlineUrl || `/api/reports/${job_id}/approve-outline`;
17506
+ setApprovingOutline(true);
17507
+ setApproveError(null);
17508
+ try {
17509
+ const headers = { "Content-Type": "application/json" };
17510
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
17511
+ const editedOutline = buildEditedOutline();
17512
+ const res = await fetch(endpoint, {
17513
+ method: "POST",
17514
+ headers,
17515
+ body: JSON.stringify(editedOutline ? { outline: editedOutline } : {})
17516
+ });
17517
+ if (!res.ok) {
17518
+ const err = await res.json().catch(() => ({}));
17519
+ throw new Error(err.detail || err.error || `Approve failed (${res.status})`);
17520
+ }
17521
+ const data = await res.json();
17522
+ if (typeof data.job_id === "string" && data.job_id !== job_id) {
17523
+ setOutlineWritePollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
17524
+ return;
17525
+ }
17526
+ setApprovingOutline(false);
17527
+ void refetchSourceAndApply();
17528
+ } catch (e) {
17529
+ setApproveError(e instanceof Error ? e.message : "Could not build the report");
17530
+ setApprovingOutline(false);
17531
+ }
17532
+ };
17017
17533
  const formatWordCount = (count) => {
17018
17534
  if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
17019
17535
  return count.toString();
@@ -17119,6 +17635,61 @@ var ResearchReportJobCard = (props) => {
17119
17635
  ] })
17120
17636
  ] }) }) });
17121
17637
  }
17638
+ if (status === "complete" && reviewStatus === "pending_outline_approval") {
17639
+ const sections = Array.isArray(outline?.sections) ? outline.sections : [];
17640
+ const building = Boolean(outlineWritePollUrl) || approvingOutline;
17641
+ return /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
17642
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
17643
+ "div",
17644
+ {
17645
+ className: "h-[3px]",
17646
+ style: { background: "linear-gradient(90deg, #8B7D5E, #C0AE82, #DCC99B)" }
17647
+ }
17648
+ ),
17649
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "p-5", children: [
17650
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-start gap-3", children: [
17651
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("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__ */ (0, import_jsx_runtime148.jsx)(DocumentIcon, {}) }),
17652
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex-1 min-w-0", children: [
17653
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("h3", { className: "text-sm font-semibold text-zinc-100 truncate leading-tight", children: title || "Report outline" }),
17654
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("p", { className: "text-xs text-amber-400/90 mt-0.5", children: [
17655
+ "Review the outline",
17656
+ sections.length > 0 ? " \u2014 edit any heading" : "",
17657
+ ", then build the full report."
17658
+ ] })
17659
+ ] })
17660
+ ] }),
17661
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "mt-4 space-y-2", children: [
17662
+ sections.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "flex items-center gap-2", children: [
17663
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { className: "text-xs text-zinc-600 w-5 text-right tabular-nums flex-shrink-0", children: i + 1 }),
17664
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
17665
+ "input",
17666
+ {
17667
+ value: headingEdits[i] ?? s.heading ?? "",
17668
+ onChange: (e) => setHeadingEdits((prev) => ({ ...prev, [i]: e.target.value })),
17669
+ disabled: building,
17670
+ 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"
17671
+ }
17672
+ )
17673
+ ] }, i)),
17674
+ sections.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("p", { className: "text-xs text-zinc-500", children: "No outline sections were returned; you can still build the report." })
17675
+ ] }),
17676
+ approveError && /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("p", { className: "mt-3 text-xs text-red-400/90", children: approveError }),
17677
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(
17678
+ "button",
17679
+ {
17680
+ onClick: handleApproveOutline,
17681
+ disabled: building,
17682
+ title: "Approve this outline and build the full report",
17683
+ 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",
17684
+ children: [
17685
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(CheckIcon2, {}),
17686
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: building ? "Building report..." : "Approve & Build" })
17687
+ ]
17688
+ }
17689
+ ) })
17690
+ ] })
17691
+ ] }) });
17692
+ }
17122
17693
  if (status === "complete" && !hasHTML) {
17123
17694
  return /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
17124
17695
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
@@ -17182,8 +17753,8 @@ var ResearchReportJobCard = (props) => {
17182
17753
  window.open(link, "_blank", "noopener,noreferrer");
17183
17754
  }
17184
17755
  };
17185
- const canApprove = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17186
- const canRegenerate = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17756
+ const canApprove = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17757
+ const canRegenerate = !hideApproval && generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
17187
17758
  const canShare = Boolean(hasHTML || job_id) && (generationMode !== "template" || reviewStatus !== "pending_review");
17188
17759
  const handleApprove = async () => {
17189
17760
  if (!job_id || approving) return;
@@ -17247,36 +17818,18 @@ var ResearchReportJobCard = (props) => {
17247
17818
  throw new Error(err.detail || err.error || `Regenerate failed (${res.status})`);
17248
17819
  }
17249
17820
  const data = await res.json();
17250
- const out = data.output || data;
17251
- if (out.html_url) setHtmlUrl(out.html_url);
17252
- if (data.template_id || out.template_id) setTemplateId(data.template_id || out.template_id);
17253
- setReviewStatus("pending_review");
17254
- setTemplateVersionId("");
17255
- if (out.section_count !== void 0) setSectionCount(out.section_count);
17256
- if (out.title) setTitle(out.title);
17257
- if (onCompleteRef.current) {
17258
- onCompleteRef.current({
17259
- title: out.title || title,
17260
- depth,
17261
- section_count: out.section_count ?? sectionCount,
17262
- source_count: sourceCount,
17263
- word_count: wordCount,
17264
- executive_summary: summary,
17265
- html_url: out.html_url || htmlUrl,
17266
- generation_mode: "template",
17267
- template_id: data.template_id || out.template_id || templateId,
17268
- template_version_id: void 0,
17269
- review_status: "pending_review",
17270
- theme
17271
- });
17821
+ if (typeof data.job_id === "string" && data.job_id !== job_id) {
17822
+ setRegenPollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
17823
+ return;
17272
17824
  }
17825
+ applyRegeneratedOutput(data.output || data);
17826
+ setRegenerating(false);
17273
17827
  } catch (e) {
17274
17828
  setApproveError(e instanceof Error ? e.message : "Could not switch template");
17275
- } finally {
17276
17829
  setRegenerating(false);
17277
17830
  }
17278
17831
  };
17279
- const templateLabel = formatTemplateLabel(templateId);
17832
+ const templateLabel = formatTemplateLabel2(templateId);
17280
17833
  const isApproved = reviewStatus === "approved";
17281
17834
  return /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(import_jsx_runtime148.Fragment, { children: [
17282
17835
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
@@ -17325,7 +17878,7 @@ var ResearchReportJobCard = (props) => {
17325
17878
  title: "Try another report template with the same data",
17326
17879
  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",
17327
17880
  children: [
17328
- /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(RefreshIcon, {}),
17881
+ /* @__PURE__ */ (0, import_jsx_runtime148.jsx)(RefreshIcon2, {}),
17329
17882
  /* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
17330
17883
  ]
17331
17884
  }