optional AI-based name generation for map entities#1358
optional AI-based name generation for map entities#1358Lupus7477 wants to merge 1 commit intoAzgaar:masterfrom
Conversation
✅ Deploy Preview for afmg ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Adds an opt-in AI-backed naming workflow across multiple map entity editors while keeping existing procedural naming as default behavior.
Changes:
- Introduces new TS modules for AI provider access + prompt-based name generation exposed via
window.AiNames - Adds UI controls (icons/buttons) to generate AI names for map entities and bulk “AI Names” regeneration
- Adds Options UI for configuring model/provider, key, temperature, language override, custom prompt, and Ollama host
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/index.ts | Registers new AI modules in the TS module bootstrap. |
| src/modules/ai-providers.ts | Implements provider-backed streaming text generation + localStorage-backed settings. |
| src/modules/ai-name-generator.ts | Builds prompts for entity naming/translation and exposes window.AiNames API. |
| src/index.html | Adds AI name generation UI elements and an Options section for AI naming configuration. |
| public/modules/ui/options.js | Wires Options inputs to localStorage and initializes AI naming settings UI. |
| public/modules/ui/tools.js | Adds “AI Names” bulk regeneration across many entity types. |
| public/modules/ui/editors/overviews.js | Adds per-entity AI naming triggers in multiple editors/overviews. |
| public/modules/ui/ai-generator.js | Updates Ollama host usage to be configurable (shared localStorage key). |
| package-lock.json | Lockfile metadata normalization (removes peer: true entries). |
| { | ||
| const optionsTab = byId("optionsTab"); | ||
| if (optionsTab) { | ||
| const origClick = optionsTab.onclick; |
| class="icon-book pointer" | ||
| ></span> | ||
| <span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span> | ||
| <span id="provinceNameEditorShortAi" data-tip="Generate name using AI" class="icon-fleur-de-lis pointer hiddenIcon"></span> |
| data-tip="Click to re-generate full name" | ||
| class="icon-arrows-cw pointer" | ||
| ></span> | ||
| <span id="provinceNameEditorFullAi" data-tip="Generate full name using AI" class="icon-fleur-de-lis pointer hiddenIcon"></span> |
|
|
||
| export function getStoredTemperature(): number { | ||
| const stored = localStorage.getItem("fmg-ai-temperature"); | ||
| return stored ? parseFloat(stored) : 1; |
| if (line === "data: [DONE]") break; | ||
|
|
||
| try { | ||
| const parsed = line.startsWith("data: ") ? JSON.parse(line.slice(6)) : JSON.parse(line); | ||
| getContent(parsed); | ||
| } catch (error) { | ||
| ERROR && console.error("Failed to parse line:", line, error); |
| function buildBatchNamePrompt(entityType: EntityType, language: string, count: number, ctx?: EntityContext): string { | ||
| const entity = ENTITY_DESCRIPTIONS[entityType]; | ||
| const context = buildContextString(entityType, ctx); | ||
| const customPrompt = getCustomPrompt(); | ||
|
|
||
| let prompt = `Generate ${count} unique fantasy names for ${entity}s in a ${language} linguistic style. All names MUST be unique — no duplicates allowed.`; | ||
| if (context) prompt += ` ${context}`; | ||
| prompt += ` Reply with ONLY the names, one per line. No numbering, no quotes, no explanation.`; |
| const AI_MODELS = { | ||
| "gpt-4o-mini": "openai", | ||
| "chatgpt-4o-latest": "openai", | ||
| "gpt-4o": "openai", | ||
| "gpt-4-turbo": "openai", | ||
| o3: "openai", | ||
| "o3-mini": "openai", | ||
| "o3-pro": "openai", | ||
| "o4-mini": "openai", | ||
| "claude-opus-4-20250514": "anthropic", | ||
| "claude-sonnet-4-20250514": "anthropic", | ||
| "claude-3-5-haiku-latest": "anthropic", | ||
| "claude-3-5-sonnet-latest": "anthropic", | ||
| "claude-3-opus-latest": "anthropic", | ||
| "ollama (local models)": "ollama" | ||
| }; | ||
|
|
||
| function initAiSettings() { | ||
| const modelSelect = byId("aiNamesModel"); | ||
| if (!modelSelect) return; | ||
|
|
There was a problem hiding this comment.
Pull request overview
Adds an opt-in AI-assisted naming feature to the Fantasy Map Generator UI and generation flow, allowing users to generate names for multiple entity types (map name, burgs, states/provinces, rivers/lakes, routes, zones, cultures, religions/deities) via configurable AI providers.
Changes:
- Introduces new TS modules for AI provider integration and prompt-based name generation exposed via
window.AiNames. - Adds UI controls (icons/buttons) across multiple editors + an Options section to configure model/provider/key/temperature/language override/custom prompt.
- Adds bulk “AI Names” regeneration action in Tools plus per-entity “generate with AI” actions in editors/overviews.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/index.ts | Loads new AI modules at startup. |
| src/modules/ai-providers.ts | Implements provider streaming calls + localStorage-backed settings. |
| src/modules/ai-name-generator.ts | Builds prompts and exposes window.AiNames helpers for entity naming. |
| src/index.html | Adds AI buttons and Options UI for AI name generation settings. |
| public/modules/ui/options.js | Persists AI settings to localStorage and initializes the Options UI. |
| public/modules/ui/tools.js | Adds “regenerate all AI names” flow for unlocked entities. |
| public/modules/ui/zones-editor.js | Adds per-zone + bulk AI zone naming. |
| public/modules/ui/routes-editor.js | Adds AI route naming with contextual prompt inputs. |
| public/modules/ui/routes-overview.js | Adds bulk AI route renaming in overview. |
| public/modules/ui/rivers-editor.js | Adds AI naming for a river in the editor. |
| public/modules/ui/rivers-overview.js | Adds bulk AI river renaming in overview. |
| public/modules/ui/lakes-editor.js | Adds AI naming for a lake in the editor. |
| public/modules/ui/burg-editor.js | Adds AI naming for a burg in the editor. |
| public/modules/ui/burgs-overview.js | Adds bulk AI burg renaming in overview. |
| public/modules/ui/provinces-editor.js | Adds AI short/full name generation in the province name editor dialog. |
| public/modules/dynamic/editors/states-editor.js | Adds AI short/full name generation + bulk AI rename for states. |
| public/modules/dynamic/editors/cultures-editor.js | Adds per-culture + bulk AI culture naming. |
| public/modules/dynamic/editors/religions-editor.js | Adds per-religion AI naming + AI deity generation + bulk AI rename. |
| public/modules/ui/ai-generator.js | Allows custom Ollama host for the existing generic AI text generator. |
| package-lock.json | Lockfile metadata changes. |
| burgName.value = await AiNames.generateName("burg", culture); | ||
| changeName(); | ||
| } catch (error) { | ||
| tip(error.message, true, "error", 4000); |
| const optionsTab = byId("optionsTab"); | ||
| if (optionsTab) { | ||
| const origClick = optionsTab.onclick; | ||
| optionsTab.addEventListener("click", function () { |
| const AI_MODELS = { | ||
| "gpt-4o-mini": "openai", | ||
| "chatgpt-4o-latest": "openai", | ||
| "gpt-4o": "openai", | ||
| "gpt-4-turbo": "openai", | ||
| o3: "openai", | ||
| "o3-mini": "openai", | ||
| "o3-pro": "openai", | ||
| "o4-mini": "openai", | ||
| "claude-opus-4-20250514": "anthropic", | ||
| "claude-sonnet-4-20250514": "anthropic", | ||
| "claude-3-5-haiku-latest": "anthropic", | ||
| "claude-3-5-sonnet-latest": "anthropic", | ||
| "claude-3-opus-latest": "anthropic", | ||
| "ollama (local models)": "ollama" | ||
| }; |
| class="icon-book pointer" | ||
| ></span> | ||
| <span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span> | ||
| <span id="provinceNameEditorShortAi" data-tip="Generate name using AI" class="icon-fleur-de-lis pointer hiddenIcon"></span> |
| data-tip="Click to re-generate full name" | ||
| class="icon-arrows-cw pointer" | ||
| ></span> | ||
| <span id="provinceNameEditorFullAi" data-tip="Generate full name using AI" class="icon-fleur-de-lis pointer hiddenIcon"></span> |
| <tr data-tip="Enter API key for selected AI provider. For Ollama enter the model name (e.g. llama3.2:3b)"> | ||
| <td></td> | ||
| <td>API Key</td> | ||
| <td> | ||
| <input id="aiNamesKey" placeholder="Enter API key" class="long" type="text" /> | ||
| </td> | ||
| <td> | ||
| <i id="aiNamesKeyHelp" data-tip="Click to see usage instructions" class="icon-help-circled pointer"></i> | ||
| </td> |
| <input data-tip="Religion form" class="religionForm" style="width: 6em" | ||
| value="${r.form}" autocorrect="off" spellcheck="false" /> | ||
| <span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw hide"></span> | ||
| <span data-tip="Generate deity name with AI" class="icon-robot-deity hiddenIcon hide" style="visibility: hidden"></span> |
| for (let i = 0; i < lines.length - 1; i++) { | ||
| const line = lines[i].trim(); | ||
| if (!line) continue; | ||
| if (line === "data: [DONE]") break; | ||
|
|
||
| try { | ||
| const parsed = line.startsWith("data: ") ? JSON.parse(line.slice(6)) : JSON.parse(line); | ||
| getContent(parsed); | ||
| } catch (error) { | ||
| ERROR && console.error("Failed to parse line:", line, error); | ||
| } | ||
| } |
| function buildBatchNamePrompt(entityType: EntityType, language: string, count: number, ctx?: EntityContext): string { | ||
| const entity = ENTITY_DESCRIPTIONS[entityType]; | ||
| const context = buildContextString(entityType, ctx); | ||
| const customPrompt = getCustomPrompt(); | ||
|
|
||
| let prompt = `Generate ${count} unique fantasy names for ${entity}s in a ${language} linguistic style. All names MUST be unique — no duplicates allowed.`; | ||
| if (context) prompt += ` ${context}`; | ||
| prompt += ` Reply with ONLY the names, one per line. No numbering, no quotes, no explanation.`; | ||
| if (customPrompt) prompt += ` Additional instructions: ${customPrompt}`; | ||
| return prompt; |
| export async function generateAiName(entityType: EntityType, cultureIndex: number, ctx?: EntityContext): Promise<string> { | ||
| if (!hasApiKey()) { | ||
| openAiSetupDialog(); | ||
| throw new Error("No API key configured"); | ||
| } |
|
Not sure I like the implementation. There is already an AI-generation dialog and we should we-use and globalize it, not add a global option, it's already very busy. |
Description
Adds optional AI-based name generation for map entities (zones, rivers, lakes, routes, burgs, states, map names).
Motivation: improve naming variety/context while keeping current behavior unchanged (opt-in only).
Closes #1357