Upgrade request analysis routing

This commit is contained in:
Andraxion 2026-06-27 01:44:11 -04:00
parent 1cd446bae8
commit db3e080640
19 changed files with 1520 additions and 66 deletions

View file

@ -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>

View file

@ -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));