Upgrade request analysis routing
This commit is contained in:
parent
1cd446bae8
commit
db3e080640
19 changed files with 1520 additions and 66 deletions
|
|
@ -20,6 +20,19 @@ type WorldDefaultPayload = {
|
|||
|
||||
type LaunchState = "ready" | "opening" | "opened" | "blocked" | "error";
|
||||
type BoardTab = "news" | "requests";
|
||||
type LauncherWindowMode = "public" | "admin";
|
||||
|
||||
type LauncherRequestAnalysisRouting = {
|
||||
summary?: string;
|
||||
ambiguity?: "low" | "medium" | "high";
|
||||
matchedTerms?: string[];
|
||||
suggestedTags?: string[];
|
||||
suggestedSystems?: string[];
|
||||
suggestedModules?: string[];
|
||||
rationale?: string;
|
||||
possibleDirections?: string[];
|
||||
kbSections?: string[];
|
||||
};
|
||||
|
||||
type LauncherRequest = {
|
||||
id: string;
|
||||
|
|
@ -40,6 +53,7 @@ type LauncherRequest = {
|
|||
error?: string;
|
||||
submissionId?: string;
|
||||
sourceTextSnapshot?: string;
|
||||
routing?: LauncherRequestAnalysisRouting;
|
||||
itemCount?: number;
|
||||
items?: Array<{
|
||||
title?: string;
|
||||
|
|
@ -99,6 +113,17 @@ type ProcessPendingPayload = {
|
|||
pid?: number;
|
||||
};
|
||||
|
||||
type RequeueAnalysisPayload = {
|
||||
ok?: boolean;
|
||||
launched?: boolean;
|
||||
reason?: string;
|
||||
request?: LauncherRequest;
|
||||
requests?: LauncherRequest[];
|
||||
requestId?: string;
|
||||
queuedPendingCount?: number;
|
||||
pid?: number;
|
||||
};
|
||||
|
||||
type LauncherRequestMetaPayload = {
|
||||
allowedTags?: string[];
|
||||
};
|
||||
|
|
@ -112,6 +137,14 @@ type AdminAuthPayload = {
|
|||
|
||||
const DEFAULT_EDITOR_WORLD_ID_FALLBACK = "overworld";
|
||||
|
||||
function readLauncherWindowMode(): LauncherWindowMode {
|
||||
if (typeof window === "undefined") {
|
||||
return "public";
|
||||
}
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
return searchParams.get("admin") === "requests" ? "admin" : "public";
|
||||
}
|
||||
|
||||
async function resolveDefaultWorldId(): Promise<string> {
|
||||
const response = await fetch("/api/world-default");
|
||||
if (!response.ok) {
|
||||
|
|
@ -186,6 +219,7 @@ function openReviewDetailsPopup(requestEntry: LauncherRequest): void {
|
|||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
const routing = requestEntry.analysis?.routing;
|
||||
const items = Array.isArray(requestEntry.analysis?.items) ? requestEntry.analysis.items : [];
|
||||
const renderedItems = items.map((item, index) => {
|
||||
const reviewOptions = Array.isArray(item?.reviewOptions) ? item.reviewOptions : [];
|
||||
|
|
@ -251,6 +285,38 @@ function openReviewDetailsPopup(requestEntry: LauncherRequest): void {
|
|||
<h1>${escapePopupHtml(requestEntry.title)}</h1>
|
||||
<p>${escapePopupHtml(requestEntry.sourceText)}</p>
|
||||
</section>
|
||||
${routing ? `
|
||||
<section class="review-card">
|
||||
<div class="review-kicker">Routing Summary</div>
|
||||
<h2>${escapePopupHtml(String(routing.summary || "KB routing context"))}</h2>
|
||||
<div class="review-meta">
|
||||
<span>Ambiguity: ${escapePopupHtml(String(routing.ambiguity || "medium"))}</span>
|
||||
${(Array.isArray(routing.suggestedTags) ? routing.suggestedTags : []).map((tag) => `<span>${escapePopupHtml(tag)}</span>`).join("")}
|
||||
</div>
|
||||
<div class="review-block">
|
||||
<h3>Rationale</h3>
|
||||
<p>${escapePopupHtml(String(routing.rationale || "No routing rationale was stored."))}</p>
|
||||
</div>
|
||||
<div class="review-block">
|
||||
<h3>Matched Terms</h3>
|
||||
${(Array.isArray(routing.matchedTerms) && routing.matchedTerms.length > 0)
|
||||
? `<ul>${routing.matchedTerms.map((term) => `<li>${escapePopupHtml(String(term || ""))}</li>`).join("")}</ul>`
|
||||
: "<p>No explicit terminology matches were stored.</p>"}
|
||||
</div>
|
||||
<div class="review-block">
|
||||
<h3>Likely Systems</h3>
|
||||
${(Array.isArray(routing.suggestedSystems) && routing.suggestedSystems.length > 0)
|
||||
? `<ul>${routing.suggestedSystems.map((systemId) => `<li>${escapePopupHtml(String(systemId || ""))}</li>`).join("")}</ul>`
|
||||
: "<p>No likely systems were stored.</p>"}
|
||||
</div>
|
||||
<div class="review-block">
|
||||
<h3>Possible Directions</h3>
|
||||
${(Array.isArray(routing.possibleDirections) && routing.possibleDirections.length > 0)
|
||||
? `<ul>${routing.possibleDirections.map((direction) => `<li>${escapePopupHtml(String(direction || ""))}</li>`).join("")}</ul>`
|
||||
: "<p>No alternate directions were stored.</p>"}
|
||||
</div>
|
||||
</section>
|
||||
` : ""}
|
||||
${renderedItems || '<section class="review-card"><h2>No Review Items</h2><p>This request does not have structured review details yet.</p></section>'}
|
||||
</main>
|
||||
</body>
|
||||
|
|
@ -369,6 +435,8 @@ function formatEventLabel(event: RecentSaveEvent): string {
|
|||
return "Queue worker finished";
|
||||
case "launcher-request-analysis-launch-error":
|
||||
return "Queue worker launch error";
|
||||
case "launcher-request-analysis-requeue":
|
||||
return "Request requeued for review";
|
||||
default:
|
||||
return String(event.type || "Event");
|
||||
}
|
||||
|
|
@ -401,12 +469,25 @@ function openRepo(): void {
|
|||
window.location.assign("https://repo.andraxion.net/");
|
||||
}
|
||||
|
||||
function openAdminPanelWindow(): boolean {
|
||||
const nextUrl = new URL(window.location.href);
|
||||
nextUrl.searchParams.set("admin", "requests");
|
||||
nextUrl.searchParams.set("tab", "requests");
|
||||
const popup = window.open(nextUrl.toString(), "worldshaper-admin-panel", "popup=yes,width=1620,height=980,resizable=yes,scrollbars=yes");
|
||||
if (popup) {
|
||||
popup.focus();
|
||||
}
|
||||
return Boolean(popup);
|
||||
}
|
||||
|
||||
function WorldshaperLauncher() {
|
||||
const launcherWindowMode = readLauncherWindowMode();
|
||||
const adminWindowMode = launcherWindowMode === "admin";
|
||||
const [launchState, setLaunchState] = useState<LaunchState>("ready");
|
||||
const [status, setStatus] = useState("Launch Worldshaper Studio in its floating window.");
|
||||
const [error, setError] = useState("");
|
||||
const [worldId, setWorldId] = useState(DEFAULT_EDITOR_WORLD_ID_FALLBACK);
|
||||
const [activeBoardTab, setActiveBoardTab] = useState<BoardTab>("news");
|
||||
const [activeBoardTab, setActiveBoardTab] = useState<BoardTab>(adminWindowMode ? "requests" : "news");
|
||||
const [requests, setRequests] = useState<LauncherRequest[]>([]);
|
||||
const [requestsLoading, setRequestsLoading] = useState(true);
|
||||
const [requestsError, setRequestsError] = useState("");
|
||||
|
|
@ -417,7 +498,6 @@ function WorldshaperLauncher() {
|
|||
const [requestFilter, setRequestFilter] = useState("all");
|
||||
const [allowedRequestTags, setAllowedRequestTags] = useState<string[]>([]);
|
||||
const [expandedRequestIds, setExpandedRequestIds] = useState<string[]>([]);
|
||||
const [adminPanelOpen, setAdminPanelOpen] = useState(false);
|
||||
const [adminAccessGranted, setAdminAccessGranted] = useState(false);
|
||||
const [adminPassword, setAdminPassword] = useState("");
|
||||
const [adminPasswordDraft, setAdminPasswordDraft] = useState("");
|
||||
|
|
@ -430,7 +510,9 @@ function WorldshaperLauncher() {
|
|||
const [logsLoading, setLogsLoading] = useState(false);
|
||||
const [logsError, setLogsError] = useState("");
|
||||
const [queueTriggering, setQueueTriggering] = useState(false);
|
||||
const [requeueingMode, setRequeueingMode] = useState<"" | "saved" | "draft">("");
|
||||
const [adminNotice, setAdminNotice] = useState("");
|
||||
const adminPanelOpen = adminWindowMode;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
|
@ -650,15 +732,11 @@ function WorldshaperLauncher() {
|
|||
setRequestDraftOpen(false);
|
||||
setRequestsError("");
|
||||
setLogsError("");
|
||||
if (adminPanelOpen) {
|
||||
setAdminPanelOpen(false);
|
||||
setAdminNotice("");
|
||||
if (adminWindowMode) {
|
||||
return;
|
||||
}
|
||||
setAdminPanelOpen(true);
|
||||
setAdminPasswordError("");
|
||||
if (adminAccessGranted && adminPassword) {
|
||||
await refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
if (!openAdminPanelWindow()) {
|
||||
setAdminNotice("Allow popups to open the admin review window.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -831,6 +909,69 @@ function WorldshaperLauncher() {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleRequeueAnalysis(mode: "saved" | "draft"): Promise<void> {
|
||||
if (!adminEditorDraft) {
|
||||
return;
|
||||
}
|
||||
setRequeueingMode(mode);
|
||||
setLogsError("");
|
||||
try {
|
||||
const payload = await fetchJsonOrThrow<RequeueAnalysisPayload>(
|
||||
`/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}/requeue-analysis`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: buildAdminHeaders(adminPassword, {
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
mode,
|
||||
request: mode === "draft"
|
||||
? {
|
||||
title: adminEditorDraft.title,
|
||||
category: adminEditorDraft.category,
|
||||
tags: adminEditorDraft.tags,
|
||||
sourceText: adminEditorDraft.sourceText,
|
||||
summary: adminEditorDraft.summary,
|
||||
implementationNotes: adminEditorDraft.implementationNotes,
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const nextRequests = Array.isArray(payload.requests) ? payload.requests : requests;
|
||||
setRequests(nextRequests);
|
||||
const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || payload.request || adminEditorDraft;
|
||||
setAdminEditorDraft(cloneLauncherRequest(refreshed));
|
||||
if (payload.launched) {
|
||||
setAdminNotice(mode === "draft"
|
||||
? "Edited draft resubmitted to the analyzer."
|
||||
: "Saved request resubmitted to the analyzer.");
|
||||
} else {
|
||||
const reason = String(payload.reason || "no-op");
|
||||
if (reason === "request-analysis-already-running") {
|
||||
setAdminNotice("The queue worker is already running. This request will be picked up on the next pass.");
|
||||
} else if (reason === "request-not-queued") {
|
||||
setAdminNotice("That request is not currently eligible for review reruns.");
|
||||
} else {
|
||||
setAdminNotice(`Review rerun returned: ${reason}.`);
|
||||
}
|
||||
}
|
||||
await refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
window.setTimeout(() => {
|
||||
void refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
}, 4200);
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to requeue this request for review."));
|
||||
} finally {
|
||||
setRequeueingMode("");
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleExpandedRequest(requestId: string): void {
|
||||
setExpandedRequestIds((current) => (
|
||||
current.includes(requestId)
|
||||
|
|
@ -941,16 +1082,20 @@ function WorldshaperLauncher() {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
const boardHint = activeBoardTab === "news"
|
||||
? "Latest announcements"
|
||||
: `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active`;
|
||||
const boardTitle = adminWindowMode ? "Worldshaper Admin" : "Worldshaper Board";
|
||||
const boardHint = adminWindowMode
|
||||
? `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active`
|
||||
: (activeBoardTab === "news"
|
||||
? "Latest announcements"
|
||||
: `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active`);
|
||||
|
||||
return (
|
||||
<main
|
||||
className="launcher-shell"
|
||||
className={`launcher-shell ${adminWindowMode ? "launcher-shell-admin" : ""}`}
|
||||
style={{ "--launcher-background-image": `url(${launcherBackground})` } as CSSProperties}
|
||||
>
|
||||
<div className="launcher-stack">
|
||||
<div className={`launcher-stack ${adminWindowMode ? "launcher-stack-admin" : ""}`}>
|
||||
{!adminWindowMode ? (
|
||||
<section className="launcher-hero-window" aria-labelledby="launcher-studio-title">
|
||||
<div className="launcher-hero-body">
|
||||
<div className="launcher-hero-stack">
|
||||
|
|
@ -987,13 +1132,15 @@ function WorldshaperLauncher() {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
<section className="launcher-changelog-window" aria-labelledby="launcher-board-title">
|
||||
<div className="launcher-changelog-titlebar">
|
||||
<div className="launcher-changelog-title" id="launcher-board-title">Worldshaper Board</div>
|
||||
<div className="launcher-changelog-title" id="launcher-board-title">{boardTitle}</div>
|
||||
<div className="launcher-changelog-hint">{boardHint}</div>
|
||||
</div>
|
||||
<div className="launcher-changelog-body">
|
||||
<div className="launcher-board-content">
|
||||
{!adminWindowMode ? (
|
||||
<div className="launcher-board-tabs">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1010,6 +1157,7 @@ function WorldshaperLauncher() {
|
|||
Requests
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{activeBoardTab === "news" ? (
|
||||
<div className="changelog-splash-card">
|
||||
<div className="changelog-splash-hero">
|
||||
|
|
@ -1046,8 +1194,8 @@ function WorldshaperLauncher() {
|
|||
) : (
|
||||
<div className="changelog-splash-card">
|
||||
<div className="changelog-splash-hero">
|
||||
<div className="changelog-splash-kicker">Shared Request Board</div>
|
||||
<div className="changelog-splash-title" id="launcher-requests-title">Requests</div>
|
||||
<div className="changelog-splash-kicker">{adminWindowMode ? "Protected Review Workspace" : "Shared Request Board"}</div>
|
||||
<div className="changelog-splash-title" id="launcher-requests-title">{adminWindowMode ? "Admin Review Console" : "Requests"}</div>
|
||||
<div className="changelog-splash-meta">
|
||||
{requestCount} saved request{requestCount === 1 ? "" : "s"}: {queuedPendingRequestCount} queued, {needsReviewRequestCount} in review, and {activeRequestCount} active.
|
||||
</div>
|
||||
|
|
@ -1057,20 +1205,21 @@ function WorldshaperLauncher() {
|
|||
type="button"
|
||||
className="launcher-primary-btn"
|
||||
onClick={() => {
|
||||
setAdminPanelOpen(false);
|
||||
setRequestDraftOpen((value) => !value);
|
||||
}}
|
||||
disabled={requestSubmitting}
|
||||
>
|
||||
{requestDraftOpen && !adminPanelOpen ? "Hide Request Form" : "Add New Request"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`launcher-secondary-btn ${adminPanelOpen ? "is-active" : ""}`}
|
||||
onClick={() => void handleAdminPanelToggle()}
|
||||
>
|
||||
{adminPanelOpen ? "Hide Admin Panel" : "Admin Panel"}
|
||||
</button>
|
||||
{!adminPanelOpen ? (
|
||||
<button
|
||||
type="button"
|
||||
className="launcher-secondary-btn"
|
||||
onClick={() => void handleAdminPanelToggle()}
|
||||
>
|
||||
Open Admin Window
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<label className="launcher-request-filter">
|
||||
<span className="launcher-request-filter-label">Filter</span>
|
||||
|
|
@ -1196,6 +1345,9 @@ function WorldshaperLauncher() {
|
|||
{reviewItem?.reviewRationale ? (
|
||||
<div className="launcher-request-admin-request-rationale">{reviewItem.reviewRationale}</div>
|
||||
) : null}
|
||||
{!reviewItem?.reviewRationale && requestEntry.analysis?.routing?.summary ? (
|
||||
<div className="launcher-request-admin-request-rationale">{requestEntry.analysis.routing.summary}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1290,6 +1442,140 @@ function WorldshaperLauncher() {
|
|||
})}
|
||||
</div>
|
||||
</label>
|
||||
<section className="launcher-request-admin-analysis-item">
|
||||
<div className="launcher-request-admin-analysis-head">
|
||||
<div>
|
||||
<div className="launcher-request-admin-kicker">Routing Pass</div>
|
||||
<div className="launcher-request-admin-request-title">KB Routing Summary</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="launcher-request-admin-editor-grid">
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Ambiguity</span>
|
||||
<select
|
||||
className="launcher-request-filter-select"
|
||||
value={String(adminEditorDraft.analysis?.routing?.ambiguity || "medium")}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
ambiguity: event.target.value as "low" | "medium" | "high",
|
||||
},
|
||||
},
|
||||
}))}
|
||||
>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Suggested Tags</span>
|
||||
<input
|
||||
type="text"
|
||||
className="launcher-request-filter-select"
|
||||
value={Array.isArray(adminEditorDraft.analysis?.routing?.suggestedTags) ? adminEditorDraft.analysis?.routing?.suggestedTags?.join(", ") : ""}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
suggestedTags: event.target.value.split(",").map((entry) => entry.trim()).filter(Boolean),
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Routing Summary</span>
|
||||
<textarea
|
||||
className="launcher-request-textarea launcher-request-admin-textarea launcher-request-admin-textarea-sm"
|
||||
value={String(adminEditorDraft.analysis?.routing?.summary || "")}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
summary: event.target.value,
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Routing Rationale</span>
|
||||
<textarea
|
||||
className="launcher-request-textarea launcher-request-admin-textarea launcher-request-admin-textarea-sm"
|
||||
value={String(adminEditorDraft.analysis?.routing?.rationale || "")}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
rationale: event.target.value,
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Matched Terms</span>
|
||||
<textarea
|
||||
className="launcher-request-textarea launcher-request-admin-textarea launcher-request-admin-textarea-sm"
|
||||
value={Array.isArray(adminEditorDraft.analysis?.routing?.matchedTerms) ? adminEditorDraft.analysis?.routing?.matchedTerms?.join("\n") : ""}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
matchedTerms: event.target.value.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean),
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Likely Systems</span>
|
||||
<textarea
|
||||
className="launcher-request-textarea launcher-request-admin-textarea launcher-request-admin-textarea-sm"
|
||||
value={Array.isArray(adminEditorDraft.analysis?.routing?.suggestedSystems) ? adminEditorDraft.analysis?.routing?.suggestedSystems?.join("\n") : ""}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
suggestedSystems: event.target.value.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean),
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="launcher-request-admin-field">
|
||||
<span className="launcher-request-filter-label">Possible Directions</span>
|
||||
<textarea
|
||||
className="launcher-request-textarea launcher-request-admin-textarea launcher-request-admin-textarea-sm"
|
||||
value={Array.isArray(adminEditorDraft.analysis?.routing?.possibleDirections) ? adminEditorDraft.analysis?.routing?.possibleDirections?.join("\n") : ""}
|
||||
onChange={(event) => updateAdminDraft((current) => ({
|
||||
...current,
|
||||
analysis: {
|
||||
...(current.analysis || {}),
|
||||
routing: {
|
||||
...(current.analysis?.routing || {}),
|
||||
possibleDirections: event.target.value.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean),
|
||||
},
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
</section>
|
||||
{(adminEditorDraft.analysis?.items || []).map((item, itemIndex) => (
|
||||
<section key={`analysis-item-${itemIndex}`} className="launcher-request-admin-analysis-item">
|
||||
<div className="launcher-request-admin-analysis-head">
|
||||
|
|
@ -1445,15 +1731,31 @@ function WorldshaperLauncher() {
|
|||
type="button"
|
||||
className="launcher-primary-btn"
|
||||
onClick={() => void handleSaveAdminRequest()}
|
||||
disabled={adminSaving}
|
||||
disabled={adminSaving || requeueingMode !== ""}
|
||||
>
|
||||
{adminSaving ? "Saving..." : "Save Review Changes"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="launcher-secondary-btn"
|
||||
onClick={() => void handleRequeueAnalysis("saved")}
|
||||
disabled={adminSaving || requeueingMode !== ""}
|
||||
>
|
||||
{requeueingMode === "saved" ? "Re-running Saved Review..." : "Review Saved Request"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="launcher-secondary-btn"
|
||||
onClick={() => void handleRequeueAnalysis("draft")}
|
||||
disabled={adminSaving || requeueingMode !== ""}
|
||||
>
|
||||
{requeueingMode === "draft" ? "Submitting Draft Review..." : "Review Edited Draft"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="launcher-secondary-btn"
|
||||
onClick={() => void handleApproveAdminRequest()}
|
||||
disabled={adminSaving}
|
||||
disabled={adminSaving || requeueingMode !== ""}
|
||||
>
|
||||
Approve Request
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ body {
|
|||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
.launcher-shell-admin {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.launcher-shell::before,
|
||||
.launcher-shell::after {
|
||||
content: "";
|
||||
|
|
@ -69,6 +73,10 @@ body {
|
|||
gap: 18px;
|
||||
}
|
||||
|
||||
.launcher-stack-admin {
|
||||
width: min(1440px, 100%);
|
||||
}
|
||||
|
||||
.launcher-hero-window,
|
||||
.launcher-changelog-window {
|
||||
border: 1px solid #4f79af;
|
||||
|
|
@ -161,6 +169,10 @@ body {
|
|||
width: min(760px, 100%);
|
||||
}
|
||||
|
||||
.launcher-shell-admin .launcher-changelog-window {
|
||||
width: min(1440px, 100%);
|
||||
}
|
||||
|
||||
.launcher-changelog-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -192,6 +204,10 @@ body {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
.launcher-shell-admin .launcher-changelog-body {
|
||||
max-height: calc(100dvh - 92px);
|
||||
}
|
||||
|
||||
.launcher-board-content {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
|
|
@ -400,6 +416,10 @@ body {
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
.launcher-shell-admin .launcher-request-admin-grid {
|
||||
grid-template-columns: 360px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.launcher-request-admin-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
|
|
@ -441,6 +461,11 @@ body {
|
|||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.launcher-shell-admin .launcher-request-admin-request-list,
|
||||
.launcher-shell-admin .launcher-request-admin-log-list {
|
||||
max-height: 42dvh;
|
||||
}
|
||||
|
||||
.launcher-request-admin-request-row,
|
||||
.launcher-request-admin-log-row {
|
||||
display: grid;
|
||||
|
|
@ -532,6 +557,10 @@ body {
|
|||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.launcher-shell-admin .launcher-request-admin-editor {
|
||||
max-height: 68dvh;
|
||||
}
|
||||
|
||||
.launcher-request-admin-editor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue