pxengine 0.1.113 → 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 +529 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.mjs +529 -16
- package/dist/index.mjs.map +1 -1
- package/dist/registry.json +62 -1
- package/package.json +1 -1
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
|
|
16444
|
-
const
|
|
16445
|
-
const
|
|
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 (
|
|
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
|
|
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
|
|
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,7 +17183,7 @@ var FullscreenPreviewModal = ({ url, title, onClose }) => {
|
|
|
16835
17183
|
) })
|
|
16836
17184
|
] });
|
|
16837
17185
|
};
|
|
16838
|
-
function
|
|
17186
|
+
function deriveJobStatusUrl2(newJobId, pollUrl, regenerateUrl) {
|
|
16839
17187
|
const base = pollUrl?.replace(/\/api\/jobs\/[^/]+\/status.*$/, "") ?? regenerateUrl?.replace(/\/api\/reports\/[^/]+\/regenerate.*$/, "") ?? "";
|
|
16840
17188
|
return `${base}/api/jobs/${newJobId}/status`;
|
|
16841
17189
|
}
|
|
@@ -16856,6 +17204,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16856
17204
|
template_id: initialTemplateId,
|
|
16857
17205
|
template_version_id: initialTemplateVersionId,
|
|
16858
17206
|
review_status: initialReviewStatus,
|
|
17207
|
+
outline: initialOutline,
|
|
16859
17208
|
theme: initialTheme,
|
|
16860
17209
|
error: initialError,
|
|
16861
17210
|
progress: initialProgress,
|
|
@@ -16863,7 +17212,9 @@ var ResearchReportJobCard = (props) => {
|
|
|
16863
17212
|
authToken,
|
|
16864
17213
|
shareUrl,
|
|
16865
17214
|
approveUrl,
|
|
17215
|
+
approveOutlineUrl,
|
|
16866
17216
|
regenerateUrl,
|
|
17217
|
+
hideApproval = false,
|
|
16867
17218
|
className,
|
|
16868
17219
|
onComplete,
|
|
16869
17220
|
onFailed,
|
|
@@ -16886,6 +17237,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16886
17237
|
const [reviewStatus, setReviewStatus] = (0, import_react79.useState)(
|
|
16887
17238
|
initialReviewStatus || (initialHtmlUrl ? "pending_review" : "")
|
|
16888
17239
|
);
|
|
17240
|
+
const [outline, setOutline] = (0, import_react79.useState)(initialOutline);
|
|
16889
17241
|
const [theme, setTheme] = (0, import_react79.useState)(initialTheme || DEFAULT_THEME);
|
|
16890
17242
|
const [error, setError] = (0, import_react79.useState)(initialError);
|
|
16891
17243
|
const [progress, setProgress] = (0, import_react79.useState)(initialProgress);
|
|
@@ -16895,6 +17247,9 @@ var ResearchReportJobCard = (props) => {
|
|
|
16895
17247
|
const [approving, setApproving] = (0, import_react79.useState)(false);
|
|
16896
17248
|
const [regenerating, setRegenerating] = (0, import_react79.useState)(false);
|
|
16897
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);
|
|
16898
17253
|
const [regenPollUrl, setRegenPollUrl] = (0, import_react79.useState)(null);
|
|
16899
17254
|
const previewRef = (0, import_react79.useRef)(null);
|
|
16900
17255
|
const onCompleteRef = (0, import_react79.useRef)(onComplete);
|
|
@@ -16924,6 +17279,10 @@ var ResearchReportJobCard = (props) => {
|
|
|
16924
17279
|
(0, import_react79.useEffect)(() => {
|
|
16925
17280
|
if (initialReviewStatus) setReviewStatus(initialReviewStatus);
|
|
16926
17281
|
}, [initialReviewStatus]);
|
|
17282
|
+
const initialOutlineSectionCount = initialOutline?.sections?.length;
|
|
17283
|
+
(0, import_react79.useEffect)(() => {
|
|
17284
|
+
if (initialOutline) setOutline(initialOutline);
|
|
17285
|
+
}, [initialOutlineSectionCount]);
|
|
16927
17286
|
(0, import_react79.useEffect)(() => {
|
|
16928
17287
|
if (initialDepth) setDepth(initialDepth);
|
|
16929
17288
|
}, [initialDepth]);
|
|
@@ -16989,6 +17348,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
16989
17348
|
}
|
|
16990
17349
|
if (newStatus === "complete" && data.output) {
|
|
16991
17350
|
const output = data.output;
|
|
17351
|
+
const isOutlinePhase = output.review_status === "pending_outline_approval" || output.type === "research-report-outline";
|
|
16992
17352
|
setTitle(output.title || initialTitle);
|
|
16993
17353
|
setDepth(output.depth || "");
|
|
16994
17354
|
setSectionCount(output.section_count || 0);
|
|
@@ -17000,10 +17360,11 @@ var ResearchReportJobCard = (props) => {
|
|
|
17000
17360
|
if (output.template_id) setTemplateId(output.template_id);
|
|
17001
17361
|
if (output.template_version_id) setTemplateVersionId(output.template_version_id);
|
|
17002
17362
|
if (output.review_status) setReviewStatus(output.review_status);
|
|
17363
|
+
if (output.outline) setOutline(output.outline);
|
|
17003
17364
|
if (output.theme) {
|
|
17004
17365
|
setTheme(output.theme);
|
|
17005
17366
|
}
|
|
17006
|
-
if (!hasNotifiedRef.current && onCompleteRef.current) {
|
|
17367
|
+
if (!isOutlinePhase && !hasNotifiedRef.current && onCompleteRef.current) {
|
|
17007
17368
|
hasNotifiedRef.current = true;
|
|
17008
17369
|
onCompleteRef.current(output);
|
|
17009
17370
|
}
|
|
@@ -17072,6 +17433,103 @@ var ResearchReportJobCard = (props) => {
|
|
|
17072
17433
|
}
|
|
17073
17434
|
}
|
|
17074
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
|
+
};
|
|
17075
17533
|
const formatWordCount = (count) => {
|
|
17076
17534
|
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
17077
17535
|
return count.toString();
|
|
@@ -17177,6 +17635,61 @@ var ResearchReportJobCard = (props) => {
|
|
|
17177
17635
|
] })
|
|
17178
17636
|
] }) }) });
|
|
17179
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
|
+
}
|
|
17180
17693
|
if (status === "complete" && !hasHTML) {
|
|
17181
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: [
|
|
17182
17695
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
@@ -17240,8 +17753,8 @@ var ResearchReportJobCard = (props) => {
|
|
|
17240
17753
|
window.open(link, "_blank", "noopener,noreferrer");
|
|
17241
17754
|
}
|
|
17242
17755
|
};
|
|
17243
|
-
const canApprove = generationMode === "template" && reviewStatus === "pending_review" && Boolean(job_id);
|
|
17244
|
-
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);
|
|
17245
17758
|
const canShare = Boolean(hasHTML || job_id) && (generationMode !== "template" || reviewStatus !== "pending_review");
|
|
17246
17759
|
const handleApprove = async () => {
|
|
17247
17760
|
if (!job_id || approving) return;
|
|
@@ -17306,7 +17819,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17306
17819
|
}
|
|
17307
17820
|
const data = await res.json();
|
|
17308
17821
|
if (typeof data.job_id === "string" && data.job_id !== job_id) {
|
|
17309
|
-
setRegenPollUrl(
|
|
17822
|
+
setRegenPollUrl(deriveJobStatusUrl2(data.job_id, pollUrl, regenerateUrl));
|
|
17310
17823
|
return;
|
|
17311
17824
|
}
|
|
17312
17825
|
applyRegeneratedOutput(data.output || data);
|
|
@@ -17316,7 +17829,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17316
17829
|
setRegenerating(false);
|
|
17317
17830
|
}
|
|
17318
17831
|
};
|
|
17319
|
-
const templateLabel =
|
|
17832
|
+
const templateLabel = formatTemplateLabel2(templateId);
|
|
17320
17833
|
const isApproved = reviewStatus === "approved";
|
|
17321
17834
|
return /* @__PURE__ */ (0, import_jsx_runtime148.jsxs)(import_jsx_runtime148.Fragment, { children: [
|
|
17322
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: [
|
|
@@ -17365,7 +17878,7 @@ var ResearchReportJobCard = (props) => {
|
|
|
17365
17878
|
title: "Try another report template with the same data",
|
|
17366
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",
|
|
17367
17880
|
children: [
|
|
17368
|
-
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(
|
|
17881
|
+
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)(RefreshIcon2, {}),
|
|
17369
17882
|
/* @__PURE__ */ (0, import_jsx_runtime148.jsx)("span", { children: regenerating ? "Switching..." : "Regenerate" })
|
|
17370
17883
|
]
|
|
17371
17884
|
}
|