Expand request review tooling and KB
This commit is contained in:
parent
ab1dfbf029
commit
cae21b61b7
16 changed files with 1258 additions and 241 deletions
97
server.js
97
server.js
|
|
@ -11,6 +11,33 @@ const app = express();
|
|||
const port = Number(process.env.PORT) || 5180;
|
||||
const host = process.env.HOST || "0.0.0.0";
|
||||
const launcherAdminPassword = String(process.env.LAUNCHER_ADMIN_PASSWORD || "").trim();
|
||||
const DEFAULT_REQUEST_TAG_DEFINITIONS = [
|
||||
{ id: "launcher", label: "Launcher", aliases: ["launcher home", "home page", "landing page", "main page"] },
|
||||
{ id: "request-board", label: "Request Board", aliases: ["requests", "request queue", "moderation", "request intake"] },
|
||||
{ id: "chunks", label: "Chunks", aliases: ["chunk", "chunk loading", "chunk storage", "chunk overview"] },
|
||||
{ id: "layers", label: "Layers", aliases: ["layer", "height layers", "z layers"] },
|
||||
{ id: "tiling", label: "Tiling", aliases: ["tile", "tile editing", "tile painting", "brush"] },
|
||||
{ id: "graphics-painter", label: "Graphics Painter", aliases: ["graphic painter", "tile art", "sprite painter", "art editor"] },
|
||||
{ id: "rendering", label: "Rendering", aliases: ["renderer", "viewport", "pixi", "draw"] },
|
||||
{ id: "animation", label: "Animation", aliases: ["animated", "timeline", "frame playback"] },
|
||||
{ id: "content", label: "Content", aliases: ["assets", "catalog", "tiles", "sprites", "images"] },
|
||||
{ id: "world-overview", label: "World Overview", aliases: ["overview", "world map", "bookmarks", "poi"] },
|
||||
{ id: "windows", label: "Windows", aliases: ["windowing", "popout", "tool windows", "floating window"] },
|
||||
{ id: "persistence", label: "Persistence", aliases: ["saving", "save pipeline", "history", "undo", "redo"] },
|
||||
{ id: "performance", label: "Performance", aliases: ["optimization", "slow", "lag", "streaming"] },
|
||||
{ id: "ui-workflow", label: "UI / Workflow", aliases: ["workflow", "ux", "ui", "quality of life", "qol", "tools", "tooling"] },
|
||||
{ id: "worlds", label: "Worlds", aliases: ["world", "world bootstrap", "startup", "world systems"] },
|
||||
{ id: "website", label: "Website", aliases: ["site", "web", "homepage"] },
|
||||
{ id: "repo", label: "Repo", aliases: ["repository", "forgejo", "git"] },
|
||||
{ id: "wiki", label: "Wiki", aliases: ["kb", "knowledge base", "docs", "documentation"] },
|
||||
{ id: "polish", label: "Polish", aliases: ["cleanup", "fit and finish", "presentation"] },
|
||||
{ id: "general", label: "General", aliases: ["broad", "misc", "cross-system"] },
|
||||
{ id: "other", label: "Other", aliases: ["unknown", "unclear", "unsorted"] },
|
||||
];
|
||||
|
||||
function normalizeRequestTagLookupValue(value) {
|
||||
return String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function isLauncherAdminProtectionEnabled() {
|
||||
return Boolean(launcherAdminPassword);
|
||||
|
|
@ -73,6 +100,7 @@ const dialogueNodeMetaPath = path.join(dataRoot, "dialogue_node_meta.json");
|
|||
const editorSettingsPath = path.join(dataRoot, "editor_settings.json");
|
||||
const launcherRequestsPath = path.join(dataRoot, "launcher_requests.json");
|
||||
const requestAnalysisWorkerScriptPath = path.join(__dirname, "scripts", "request-analysis-worker.mjs");
|
||||
const requestTagCatalogPath = path.join(__dirname, "docs", "kb", "tags.json");
|
||||
const requestAnalysisRunState = {
|
||||
child: null,
|
||||
restartTimer: null,
|
||||
|
|
@ -81,6 +109,17 @@ const imagesCatalogPath = path.join(contentRoot, "images.json");
|
|||
const legacyTilesCatalogPath = path.join(contentRoot, "tiles.json");
|
||||
const legacySpritesCatalogPath = path.join(contentRoot, "sprites.json");
|
||||
const recentSaveEvents = [];
|
||||
const REQUEST_TAG_DEFINITIONS = loadRequestTagCatalog();
|
||||
const REQUEST_TAG_LABELS = REQUEST_TAG_DEFINITIONS.map((entry) => entry.label);
|
||||
const REQUEST_TAG_LOOKUP = new Map(
|
||||
REQUEST_TAG_DEFINITIONS.flatMap((entry) => {
|
||||
const normalizedLabel = normalizeRequestTagLookupValue(entry.label);
|
||||
return [
|
||||
[normalizedLabel, entry.label],
|
||||
...entry.aliases.map((alias) => [normalizeRequestTagLookupValue(alias), entry.label]),
|
||||
];
|
||||
}),
|
||||
);
|
||||
const DEFAULT_WORLDSHAPER_THEME_PRESET = "azure";
|
||||
const WORLDSHAPER_THEME_PRESET_IDS = new Set(["azure", "verdant", "ember", "amethyst"]);
|
||||
const LEGACY_LAUNCHER_REQUEST_MIGRATIONS = {
|
||||
|
|
@ -417,8 +456,8 @@ function normalizeLauncherRequestStatus(value) {
|
|||
|
||||
function normalizeLauncherRequestTags(value) {
|
||||
return normalizeUniqueStringList(value, {
|
||||
normalizeValue: (entry) => String(entry || "").replace(/\s+/g, " ").trim(),
|
||||
dedupeKey: (entry) => String(entry || "").replace(/\s+/g, " ").trim().toLowerCase(),
|
||||
normalizeValue: (entry) => normalizeRequestTag(entry),
|
||||
dedupeKey: (entry) => normalizeRequestTag(entry).toLowerCase(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -445,6 +484,14 @@ function normalizeLauncherRequestAnalysisStringList(value) {
|
|||
});
|
||||
}
|
||||
|
||||
function normalizeLauncherRequestAnalysisTags(value, fallback = []) {
|
||||
const normalized = normalizeLauncherRequestTags(Array.isArray(value) ? value : []);
|
||||
if (normalized.length > 0) {
|
||||
return normalized;
|
||||
}
|
||||
return normalizeLauncherRequestTags(Array.isArray(fallback) ? fallback : []);
|
||||
}
|
||||
|
||||
function normalizeLauncherRequestAnalysisItem(item, index = 0) {
|
||||
const source = item && typeof item === "object" && !Array.isArray(item)
|
||||
? item
|
||||
|
|
@ -455,9 +502,11 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
|
|||
const fallbackTitle = `Analyzed request ${index + 1}`;
|
||||
const title = String(source.title || "").trim() || fallbackTitle;
|
||||
const primaryCategory = String(source.primaryCategory || source.category || "").trim() || "Unsorted";
|
||||
const tags = normalizeLauncherRequestAnalysisStringList(source.tags);
|
||||
const tags = normalizeLauncherRequestAnalysisTags(source.tags, [primaryCategory]);
|
||||
const parsedInterpretation = String(source.parsedInterpretation || source.summary || "").trim();
|
||||
const implementationApproach = String(source.implementationApproach || source.implementationNotes || "").trim();
|
||||
const reviewRationale = String(source.reviewRationale || source.reviewReason || "").trim();
|
||||
const reviewOptions = normalizeLauncherRequestAnalysisStringList(source.reviewOptions);
|
||||
const statusRecommendationRaw = String(source.statusRecommendation || source.status || "").trim().toLowerCase();
|
||||
const statusRecommendation = statusRecommendationRaw === "active"
|
||||
|| statusRecommendationRaw === "duplicate"
|
||||
|
|
@ -480,7 +529,7 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
|
|||
return {
|
||||
title,
|
||||
primaryCategory,
|
||||
tags,
|
||||
tags: tags.length > 0 ? tags : ["General"],
|
||||
statusRecommendation,
|
||||
parsedInterpretation,
|
||||
implementationApproach,
|
||||
|
|
@ -489,6 +538,8 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
|
|||
problemType,
|
||||
rawExcerpt: String(source.rawExcerpt || "").trim(),
|
||||
confidence: normalizeLauncherRequestAnalysisConfidence(source.confidence),
|
||||
reviewRationale,
|
||||
reviewOptions,
|
||||
notes: String(source.notes || "").trim(),
|
||||
};
|
||||
}
|
||||
|
|
@ -2166,6 +2217,37 @@ function normalizeUniqueStringList(value, options = {}) {
|
|||
return normalized;
|
||||
}
|
||||
|
||||
function loadRequestTagCatalog() {
|
||||
try {
|
||||
const payload = JSON.parse(fs.readFileSync(requestTagCatalogPath, "utf8"));
|
||||
const tags = Array.isArray(payload?.tags)
|
||||
? payload.tags
|
||||
.map((entry) => ({
|
||||
id: String(entry?.id || "").trim(),
|
||||
label: String(entry?.label || "").trim(),
|
||||
aliases: Array.isArray(entry?.aliases)
|
||||
? entry.aliases.map((alias) => String(alias || "").trim()).filter(Boolean)
|
||||
: [],
|
||||
}))
|
||||
.filter((entry) => entry.id && entry.label)
|
||||
: [];
|
||||
if (tags.length > 0) {
|
||||
return tags;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to built-in definitions when the KB file is unavailable.
|
||||
}
|
||||
return DEFAULT_REQUEST_TAG_DEFINITIONS;
|
||||
}
|
||||
|
||||
function normalizeRequestTag(value) {
|
||||
const normalizedValue = normalizeRequestTagLookupValue(value);
|
||||
if (!normalizedValue) {
|
||||
return "";
|
||||
}
|
||||
return REQUEST_TAG_LOOKUP.get(normalizedValue) || "";
|
||||
}
|
||||
|
||||
function normalizeTagList(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
|
|
@ -2710,6 +2792,13 @@ app.get("/api/debug/recent-saves", (req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
app.get("/api/launcher-request-meta", (_req, res) => {
|
||||
res.json({
|
||||
ok: true,
|
||||
allowedTags: REQUEST_TAG_LABELS,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/launcher-requests", (_req, res) => {
|
||||
try {
|
||||
res.json(readLauncherRequestsPayload());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue