improve and fix ci stuff. cleanup debug
This commit is contained in:
@@ -21,11 +21,29 @@ function parseList(raw) {
|
||||
function parseObjects(raw, type = "rooms") {
|
||||
const cleanedRaw = raw.replace(/Intermediate Rooms:/i, "").replace(/Climax Room:/i, "").trim();
|
||||
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 desc = descParts.join(" ").trim();
|
||||
const obj = { name: name.trim() };
|
||||
if (type === "rooms") return { ...obj, description: desc };
|
||||
if (type === "encounters") return { ...obj, details: desc };
|
||||
if (type === "npcs") return { ...obj, trait: desc };
|
||||
if (type === "treasure") return { ...obj, description: desc };
|
||||
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
|
||||
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")
|
||||
- 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)
|
||||
@@ -491,7 +509,7 @@ Each resolution should:
|
||||
- Integrate NPCs, faction dynamics, and player actions
|
||||
- Include failure states or unexpected outcomes as options
|
||||
- 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"
|
||||
);
|
||||
const plotResolutions = parseList(plotResolutionsRaw);
|
||||
|
||||
@@ -169,6 +169,8 @@ export function dungeonTemplate(data) {
|
||||
vertical-align: top;
|
||||
line-height: 1.3em;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
table tr:last-child td {
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
@@ -181,12 +183,14 @@ export function dungeonTemplate(data) {
|
||||
}
|
||||
.encounters-table td:nth-child(2) {
|
||||
font-weight: bold;
|
||||
width: 30%;
|
||||
width: 25%;
|
||||
padding-right: 0.5em;
|
||||
border-right: 1px solid #1a1a1a;
|
||||
}
|
||||
.encounters-table td:nth-child(3) {
|
||||
width: auto;
|
||||
font-size: 0.75em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
.map-page {
|
||||
display: flex;
|
||||
@@ -265,13 +269,35 @@ export function dungeonTemplate(data) {
|
||||
<h2>Encounters (d6)</h2>
|
||||
<table class="encounters-table">
|
||||
<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>
|
||||
<td>${index + 1}</td>
|
||||
<td><strong>${encounter.name}</strong></td>
|
||||
<td>${encounter.details}</td>
|
||||
<td>${details}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -304,11 +330,31 @@ export function dungeonTemplate(data) {
|
||||
${data.plotResolutions && data.plotResolutions.length > 0 ? `
|
||||
<div class="section-block">
|
||||
<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">
|
||||
${resolution}
|
||||
${text}
|
||||
</div>
|
||||
`).join('')}
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
7
index.js
7
index.js
@@ -2,7 +2,7 @@ import "dotenv/config";
|
||||
import { generateDungeon } from "./dungeonGenerator.js";
|
||||
import { generateDungeonImages } from "./imageGenerator.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
|
||||
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 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
|
||||
const dungeonData = await generateDungeon();
|
||||
|
||||
|
||||
@@ -47,13 +47,6 @@ async function callOllamaBase(prompt, model, retries, stepName, apiType) {
|
||||
? { model, messages: [{ role: "user", content: prompt }] }
|
||||
: { 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, {
|
||||
method: "POST",
|
||||
headers,
|
||||
@@ -68,14 +61,9 @@ async function callOllamaBase(prompt, model, retries, stepName, apiType) {
|
||||
} catch {
|
||||
// Ignore errors reading error response
|
||||
}
|
||||
const errorMsg = `Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`;
|
||||
if (isUsingOpenWebUI) {
|
||||
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);
|
||||
throw new Error(
|
||||
`Ollama request failed: ${response.status} ${response.statusText}${errorDetails}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@@ -122,40 +110,3 @@ export async function callOllamaExplicit(
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user