improve and fix ci stuff. cleanup debug

This commit is contained in:
2026-01-11 21:41:30 -05:00
parent c7bb0f04df
commit 3b91ce3068
4 changed files with 78 additions and 68 deletions

View File

@@ -21,11 +21,29 @@ function parseList(raw) {
function parseObjects(raw, type = "rooms") { function parseObjects(raw, type = "rooms") {
const cleanedRaw = raw.replace(/Intermediate Rooms:/i, "").replace(/Climax Room:/i, "").trim(); const cleanedRaw = raw.replace(/Intermediate Rooms:/i, "").replace(/Climax Room:/i, "").trim();
const mapper = (entry) => { const mapper = (entry) => {
if (type === "encounters") {
// For encounters, format is "Encounter Name: Location Name: details"
// We want to keep the encounter name separate and preserve the location:details structure
const parts = entry.split(/:/);
if (parts.length >= 3) {
// Format: "Encounter Name: Location Name: details"
return {
name: parts[0].trim(),
details: parts.slice(1).join(":").trim() // Keep "Location Name: details"
};
} else if (parts.length === 2) {
// Format: "Encounter Name: details" (fallback)
return {
name: parts[0].trim(),
details: parts[1].trim()
};
}
}
// For other types, use original logic
const [name, ...descParts] = entry.split(/[-–—:]/); const [name, ...descParts] = entry.split(/[-–—:]/);
const desc = descParts.join(" ").trim(); const desc = descParts.join(" ").trim();
const obj = { name: name.trim() }; const obj = { name: name.trim() };
if (type === "rooms") return { ...obj, description: desc }; if (type === "rooms") return { ...obj, description: desc };
if (type === "encounters") return { ...obj, details: desc };
if (type === "npcs") return { ...obj, trait: desc }; if (type === "npcs") return { ...obj, trait: desc };
if (type === "treasure") return { ...obj, description: desc }; if (type === "treasure") return { ...obj, description: desc };
return entry; return entry;
@@ -373,7 +391,7 @@ Generate the rest of the dungeon's content to fill the space between the entranc
- Hidden aspects discoverable through interaction or investigation - Hidden aspects discoverable through interaction or investigation
Format as "Name: description" using colons, NOT em-dashes. Format as "Name: description" using colons, NOT em-dashes.
- **Strictly 6 Encounters:** Numbered 1-6 (for d6 rolling). Name (max 6 words) and details (90-120 words per encounter). Each encounter must: - **Strictly 6 Encounters:** Numbered 1-6 (for d6 rolling). Name (max 6 words) and details (2-3 sentences MAX, approximately 30-50 words). Each encounter must:
- Start with the room/location name followed by a colon, then the details (e.g., "Location Name: Details text") - Start with the room/location name followed by a colon, then the details (e.g., "Location Name: Details text")
- The location name must match one of the actual room names from this dungeon - The location name must match one of the actual room names from this dungeon
- Include environmental hazards/opportunities (cover, elevation, traps, interactable objects, terrain features) - Include environmental hazards/opportunities (cover, elevation, traps, interactable objects, terrain features)
@@ -491,7 +509,7 @@ Each resolution should:
- Integrate NPCs, faction dynamics, and player actions - Integrate NPCs, faction dynamics, and player actions
- Include failure states or unexpected outcomes as options - Include failure states or unexpected outcomes as options
- Reflect different approaches players might take - Reflect different approaches players might take
Keep each item to 50-60 words (2-3 sentences). Output as a numbered list, plain text only. Do not use em-dashes (—) anywhere in the output. Absolutely do not use the word "Obsidian" or "obsidian" anywhere in the output.`, Keep each item to 1-2 sentences MAX (approximately 20-30 words). Be extremely concise. Output as a numbered list, plain text only. Do not use em-dashes (—) anywhere in the output. Absolutely do not use the word "Obsidian" or "obsidian" anywhere in the output.`,
undefined, 5, "Step 6: Plot Resolutions" undefined, 5, "Step 6: Plot Resolutions"
); );
const plotResolutions = parseList(plotResolutionsRaw); const plotResolutions = parseList(plotResolutionsRaw);

View File

@@ -169,6 +169,8 @@ export function dungeonTemplate(data) {
vertical-align: top; vertical-align: top;
line-height: 1.3em; line-height: 1.3em;
border-bottom: 1px solid #1a1a1a; border-bottom: 1px solid #1a1a1a;
word-wrap: break-word;
overflow-wrap: break-word;
} }
table tr:last-child td { table tr:last-child td {
border-bottom: 1px solid #1a1a1a; border-bottom: 1px solid #1a1a1a;
@@ -181,12 +183,14 @@ export function dungeonTemplate(data) {
} }
.encounters-table td:nth-child(2) { .encounters-table td:nth-child(2) {
font-weight: bold; font-weight: bold;
width: 30%; width: 25%;
padding-right: 0.5em; padding-right: 0.5em;
border-right: 1px solid #1a1a1a; border-right: 1px solid #1a1a1a;
} }
.encounters-table td:nth-child(3) { .encounters-table td:nth-child(3) {
width: auto; width: auto;
font-size: 0.75em;
line-height: 1.2em;
} }
.map-page { .map-page {
display: flex; display: flex;
@@ -265,13 +269,35 @@ export function dungeonTemplate(data) {
<h2>Encounters (d6)</h2> <h2>Encounters (d6)</h2>
<table class="encounters-table"> <table class="encounters-table">
<tbody> <tbody>
${data.encounters.map((encounter, index) => ` ${data.encounters.map((encounter, index) => {
// Truncate details to 2-3 sentences max to prevent overflow
let details = encounter.details || '';
// Remove location prefix from details if present (format: "Location Name: details")
details = details.replace(/^[^:]+:\s*/, '');
// Split into sentences and keep only first 3
const sentences = details.match(/[^.!?]+[.!?]+/g) || [details];
if (sentences.length > 3) {
details = sentences.slice(0, 3).join(' ').trim();
}
// Also limit by character count as fallback (max ~250 chars)
if (details.length > 250) {
details = details.substring(0, 247).trim();
// Try to end at a sentence boundary
const lastPeriod = details.lastIndexOf('.');
if (lastPeriod > 200) {
details = details.substring(0, lastPeriod + 1);
} else {
details += '...';
}
}
return `
<tr> <tr>
<td>${index + 1}</td> <td>${index + 1}</td>
<td><strong>${encounter.name}</strong></td> <td><strong>${encounter.name}</strong></td>
<td>${encounter.details}</td> <td>${details}</td>
</tr> </tr>
`).join('')} `;
}).join('')}
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -304,11 +330,31 @@ export function dungeonTemplate(data) {
${data.plotResolutions && data.plotResolutions.length > 0 ? ` ${data.plotResolutions && data.plotResolutions.length > 0 ? `
<div class="section-block"> <div class="section-block">
<h2>Plot Resolutions</h2> <h2>Plot Resolutions</h2>
${data.plotResolutions.map(resolution => ` ${data.plotResolutions.map(resolution => {
// Truncate to 1-2 sentences max to prevent overflow
let text = resolution || '';
// Split into sentences and keep only first 2
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
if (sentences.length > 2) {
text = sentences.slice(0, 2).join(' ').trim();
}
// Also limit by character count as fallback (max ~150 chars)
if (text.length > 150) {
text = text.substring(0, 147).trim();
// Try to end at a sentence boundary
const lastPeriod = text.lastIndexOf('.');
if (lastPeriod > 100) {
text = text.substring(0, lastPeriod + 1);
} else {
text += '...';
}
}
return `
<div class="plot-resolution"> <div class="plot-resolution">
${resolution} ${text}
</div> </div>
`).join('')} `;
}).join('')}
</div> </div>
` : ''} ` : ''}
</div> </div>

View File

@@ -2,7 +2,7 @@ import "dotenv/config";
import { generateDungeon } from "./dungeonGenerator.js"; import { generateDungeon } from "./dungeonGenerator.js";
import { generateDungeonImages } from "./imageGenerator.js"; import { generateDungeonImages } from "./imageGenerator.js";
import { generatePDF } from "./generatePDF.js"; import { generatePDF } from "./generatePDF.js";
import { OLLAMA_MODEL, listOpenWebUIModels } from "./ollamaClient.js"; import { OLLAMA_MODEL } from "./ollamaClient.js";
// Utility to create a filesystem-safe filename from the dungeon title // Utility to create a filesystem-safe filename from the dungeon title
function slugify(text) { function slugify(text) {
@@ -20,11 +20,6 @@ function slugify(text) {
console.log("Using Ollama API URL:", process.env.OLLAMA_API_URL); console.log("Using Ollama API URL:", process.env.OLLAMA_API_URL);
console.log("Using Ollama model:", OLLAMA_MODEL); console.log("Using Ollama model:", OLLAMA_MODEL);
// Try to list available models if using Open WebUI
if (process.env.OLLAMA_API_URL?.includes("/api/chat/completions")) {
await listOpenWebUIModels();
}
// Generate the dungeon data // Generate the dungeon data
const dungeonData = await generateDungeon(); const dungeonData = await generateDungeon();

View File

@@ -47,13 +47,6 @@ async function callOllamaBase(prompt, model, retries, stepName, apiType) {
? { model, messages: [{ role: "user", content: prompt }] } ? { model, messages: [{ role: "user", content: prompt }] }
: { model, prompt, stream: false }; : { model, prompt, stream: false };
// Debug logging for Open WebUI
if (isUsingOpenWebUI) {
console.log(`[${stepName}] Using Open WebUI API`);
console.log(`[${stepName}] Model name: "${model}"`);
console.log(`[${stepName}] API URL: ${OLLAMA_API_URL}`);
}
const response = await fetch(OLLAMA_API_URL, { const response = await fetch(OLLAMA_API_URL, {
method: "POST", method: "POST",
headers, headers,
@@ -68,14 +61,9 @@ async function callOllamaBase(prompt, model, retries, stepName, apiType) {
} catch { } catch {
// Ignore errors reading error response // Ignore errors reading error response
} }
const errorMsg = `Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`; throw new Error(
if (isUsingOpenWebUI) { `Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`,
console.error(`[${stepName}] Request details:`); );
console.error(` URL: ${OLLAMA_API_URL}`);
console.error(` Model: "${model}"`);
console.error(` Body: ${JSON.stringify(body, null, 2)}`);
}
throw new Error(errorMsg);
} }
const data = await response.json(); const data = await response.json();
@@ -122,40 +110,3 @@ export async function callOllamaExplicit(
) { ) {
return callOllamaBase(prompt, model, retries, stepName, apiType); return callOllamaBase(prompt, model, retries, stepName, apiType);
} }
// Helper function to list available models from Open WebUI
export async function listOpenWebUIModels() {
if (!OLLAMA_API_URL || !OLLAMA_API_URL.includes("/api/chat/completions")) {
console.log("Not using Open WebUI API, skipping model list");
return null;
}
try {
const headers = { "Content-Type": "application/json" };
if (OLLAMA_API_KEY) {
headers["Authorization"] = `Bearer ${OLLAMA_API_KEY}`;
}
// Try to get models from Open WebUI's models endpoint
// Open WebUI might have a /api/v1/models endpoint
const baseUrl = OLLAMA_API_URL.replace("/api/chat/completions", "");
const modelsUrl = `${baseUrl}/api/v1/models`;
const response = await fetch(modelsUrl, {
method: "GET",
headers,
});
if (response.ok) {
const data = await response.json();
console.log("Available models from Open WebUI:", JSON.stringify(data, null, 2));
return data;
} else {
console.log(`Could not list models: ${response.status} ${response.statusText}`);
return null;
}
} catch (err) {
console.log(`Error listing models: ${err.message}`);
return null;
}
}