Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions public/modules/dynamic/editors/cultures-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function insertEditorHtml() {
</div>
<button id="culturesEditNamesBase" data-tip="Edit a database used for names generation" class="icon-font"></button>
<button id="culturesAdd" data-tip="Add a new culture. Hold Shift to add multiple" class="icon-plus"></button>
<button id="culturesRegenerateNamesAi" data-tip="Regenerate culture names using AI" class="icon-robot"></button>
<button id="culturesExport" data-tip="Download cultures-related data" class="icon-download"></button>
<button id="culturesImport" data-tip="Upload cultures-related data" class="icon-upload"></button>
<button id="culturesRecalculate" data-tip="Recalculate cultures based on current values of growth-related attributes" class="icon-retweet"></button>
Expand Down Expand Up @@ -87,6 +88,7 @@ function addListeners() {
byId("culturesManuallyCancel").on("click", () => exitCulturesManualAssignment());
byId("culturesEditNamesBase").on("click", editNamesbase);
byId("culturesAdd").on("click", enterAddCulturesMode);
byId("culturesRegenerateNamesAi").on("click", regenerateCultureNamesAi);
byId("culturesExport").on("click", downloadCulturesCsv);
byId("culturesImport").on("click", () => byId("culturesCSVToLoad").click());
byId("culturesCSVToLoad").on("change", uploadCulturesData);
Expand Down Expand Up @@ -190,6 +192,7 @@ function culturesEditorAddLines() {
<input data-tip="Culture name. Click and type to change" class="cultureName" style="width: 7em"
value="${c.name}" autocorrect="off" spellcheck="false" />
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
<span data-tip="Generate culture name with AI" class="icon-robot hiddenIcon" style="visibility: hidden"></span>
<select data-tip="Culture type. Defines growth model. Click to change"
class="cultureType">${getTypeOptions(c.type)}</select>
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
Expand Down Expand Up @@ -236,6 +239,7 @@ function culturesEditorAddLines() {
$body.querySelectorAll("fill-box").forEach($el => $el.on("click", cultureChangeColor));
$body.querySelectorAll("div > input.cultureName").forEach($el => $el.on("input", cultureChangeName));
$body.querySelectorAll("div > span.icon-cw").forEach($el => $el.on("click", cultureRegenerateName));
$body.querySelectorAll("div > span.icon-robot").forEach($el => $el.on("click", cultureRegenerateNameAi));
$body.querySelectorAll("div > input.cultureExpan").forEach($el => $el.on("change", cultureChangeExpansionism));
$body.querySelectorAll("div > select.cultureType").forEach($el => $el.on("change", cultureChangeType));
$body.querySelectorAll("div > select.cultureBase").forEach($el => $el.on("change", cultureChangeBase));
Expand Down Expand Up @@ -354,6 +358,20 @@ function cultureRegenerateName() {
pack.cultures[cultureId].name = name;
}

async function cultureRegenerateNameAi() {
const cultureId = +this.parentNode.dataset.id;
const $line = this.parentNode;
try {
const name = await AiNames.generateName("culture", cultureId);
$line.querySelector("input.cultureName").value = name;
$line.dataset.name = name;
pack.cultures[cultureId].name = name;
pack.cultures[cultureId].code = abbreviate(name, pack.cultures.map(c => c.code));
} catch (err) {
if (err.message !== "No API key configured") tip("AI name generation failed: " + err.message, false, "error", 4000);
}
}

function cultureChangeExpansionism() {
const culture = +this.parentNode.dataset.id;
this.parentNode.dataset.expansionism = this.value;
Expand Down Expand Up @@ -850,6 +868,32 @@ function closeCulturesEditor() {
exitAddCultureMode();
}

async function regenerateCultureNamesAi() {
const elements = Array.from($body.querySelectorAll(":scope > div"));
const unlocked = elements.filter(el => {
const id = +el.dataset.id;
return id > 0 && !pack.cultures[id].lock;
});
if (!unlocked.length) return;

tip("Generating AI names...", false, "info");

try {
for (const el of unlocked) {
const cultureId = +el.dataset.id;
const names = await AiNames.generateNames("culture", cultureId, 1);
const name = names[0] || Names.getCulture(cultureId);
el.querySelector("input.cultureName").value = name;
el.dataset.name = name;
pack.cultures[cultureId].name = name;
pack.cultures[cultureId].code = abbreviate(name, pack.cultures.map(c => c.code));
}
tip("AI names generated successfully", true, "success", 3000);
} catch (error) {
tip(error.message, true, "error", 4000);
}
}

async function uploadCulturesData() {
const file = this.files[0];
this.value = "";
Expand Down
74 changes: 74 additions & 0 deletions public/modules/dynamic/editors/religions-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function insertEditorHtml() {
</div>
<button id="religionsAdd" data-tip="Add a new religion. Hold Shift to add multiple" class="icon-plus"></button>
<button id="religionsExport" data-tip="Download religions-related data" class="icon-download"></button>
<button id="religionsRegenerateNamesAi" data-tip="Regenerate religion names using AI" class="icon-robot"></button>
<button id="religionsRecalculate" data-tip="Recalculate religions based on current values of growth-related attributes" class="icon-retweet"></button>
<span data-tip="Allow religion center, extent, and expansionism changes to take an immediate effect">
<input id="religionsAutoChange" class="checkbox" type="checkbox" />
Expand All @@ -100,6 +101,7 @@ function addListeners() {
byId("religionsManuallyCancel").on("click", () => exitReligionsManualAssignment());
byId("religionsAdd").on("click", enterAddReligionMode);
byId("religionsExport").on("click", downloadReligionsCsv);
byId("religionsRegenerateNamesAi").on("click", regenerateReligionNamesAi);
byId("religionsRecalculate").on("click", () => recalculateReligions(true));
}

Expand Down Expand Up @@ -196,12 +198,14 @@ function religionsEditorAddLines() {
<fill-box fill="${r.color}"></fill-box>
<input data-tip="Religion name. Click and type to change" class="religionName" style="width: 11em"
value="${r.name}" autocorrect="off" spellcheck="false" />
<span data-tip="Generate religion name with AI" class="icon-robot hiddenIcon" style="visibility: hidden"></span>
<select data-tip="Religion type" class="religionType" style="width: 5em">
${getTypeOptions(r.type)}
</select>
<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>
<input data-tip="Religion supreme deity" class="religionDeity hide" style="width: 17em"
value="${r.deity || ""}" autocorrect="off" spellcheck="false" />
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
Expand Down Expand Up @@ -236,10 +240,12 @@ function religionsEditorAddLines() {
});
$body.querySelectorAll("fill-box").forEach(el => el.on("click", religionChangeColor));
$body.querySelectorAll("div > input.religionName").forEach(el => el.on("input", religionChangeName));
$body.querySelectorAll("div > span.icon-robot").forEach(el => el.on("click", religionRegenerateNameAi));
$body.querySelectorAll("div > select.religionType").forEach(el => el.on("change", religionChangeType));
$body.querySelectorAll("div > input.religionForm").forEach(el => el.on("input", religionChangeForm));
$body.querySelectorAll("div > input.religionDeity").forEach(el => el.on("input", religionChangeDeity));
$body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.on("click", regenerateDeity));
$body.querySelectorAll("div > span.icon-robot-deity").forEach(el => el.on("click", regenerateDeityAi));
$body.querySelectorAll("div > div.religionPopulation").forEach(el => el.on("click", changePopulation));
$body.querySelectorAll("div > select.religionExtent").forEach(el => el.on("change", religionChangeExtent));
$body.querySelectorAll("div > input.religionExpantion").forEach(el => el.on("change", religionChangeExpansionism));
Expand Down Expand Up @@ -363,6 +369,21 @@ function religionChangeName() {
);
}

async function religionRegenerateNameAi() {
const religionId = +this.parentNode.dataset.id;
const $line = this.parentNode;
const cultureId = pack.religions[religionId].culture;
try {
const name = await AiNames.generateName("religion", cultureId);
$line.querySelector("input.religionName").value = name;
$line.dataset.name = name;
pack.religions[religionId].name = name;
pack.religions[religionId].code = abbreviate(name, pack.religions.map(r => r.code));
} catch (err) {
if (err.message !== "No API key configured") tip("AI name generation failed: " + err.message, false, "error", 4000);
}
}

function religionChangeType() {
const religionId = +this.parentNode.dataset.id;
this.parentNode.dataset.type = this.value;
Expand Down Expand Up @@ -390,6 +411,23 @@ function regenerateDeity() {
this.nextElementSibling.value = deity;
}

async function regenerateDeityAi() {
const religionId = +this.parentNode.dataset.id;
const religion = pack.religions[religionId];
const cultureId = religion.culture;
try {
const deity = await AiNames.generateName("deity", cultureId, {
religionType: religion.type,
religionForm: religion.form
});
this.parentNode.dataset.deity = deity;
pack.religions[religionId].deity = deity;
this.nextElementSibling.value = deity;
} catch (err) {
if (err.message !== "No API key configured") tip("AI deity generation failed: " + err.message, false, "error", 4000);
}
}

function changePopulation() {
const religionId = +this.parentNode.dataset.id;
const religion = pack.religions[religionId];
Expand Down Expand Up @@ -821,6 +859,42 @@ function closeReligionsEditor() {
exitAddReligionMode();
}

async function regenerateReligionNamesAi() {
const elements = Array.from($body.querySelectorAll(":scope > div"));
const unlocked = elements.filter(el => {
const id = +el.dataset.id;
return id > 0 && !pack.religions[id].lock;
});
if (!unlocked.length) return;

const byCulture = new Map();
for (const el of unlocked) {
const religionId = +el.dataset.id;
const culture = pack.religions[religionId].culture;
if (!byCulture.has(culture)) byCulture.set(culture, []);
byCulture.get(culture).push({el, religionId});
}

tip("Generating AI names...", false, "info");

try {
for (const [culture, religions] of byCulture) {
const names = await AiNames.generateNames("religion", culture, religions.length);
for (let i = 0; i < religions.length; i++) {
const name = names[i] || Religions.getRandomName(culture);
const {el, religionId} = religions[i];
el.querySelector("input.religionName").value = name;
el.dataset.name = name;
pack.religions[religionId].name = name;
pack.religions[religionId].code = abbreviate(name, pack.religions.map(r => r.code));
}
}
tip("AI names generated successfully", true, "success", 3000);
} catch (error) {
tip(error.message, true, "error", 4000);
}
}

function updateLockStatus() {
if (customization) return;

Expand Down
64 changes: 64 additions & 0 deletions public/modules/dynamic/editors/states-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function insertEditorHtml() {
<button id="statesManuallyCancel" data-tip="Cancel assignment" class="icon-cancel"></button>
</div>

<button id="statesRegenerateNamesAi" data-tip="Regenerate state names using AI" class="icon-robot"></button>
<button id="statesAdd" data-tip="Add a new state. Hold Shift to add multiple" class="icon-plus"></button>
<button id="statesMerge" data-tip="Merge several states into one" class="icon-layer-group"></button>
<button id="statesExport" data-tip="Save state-related data as a text file (.csv)" class="icon-download"></button>
Expand Down Expand Up @@ -106,6 +107,7 @@ function addListeners() {
byId("statesManuallyCancel").on("click", () => exitStatesManualAssignment(false));
byId("statesAdd").on("click", enterAddStateMode);
byId("statesMerge").on("click", openStateMergeDialog);
byId("statesRegenerateNamesAi").on("click", regenerateStateNamesAi);
byId("statesExport").on("click", downloadStatesCsv);

$body.on("click", event => {
Expand Down Expand Up @@ -400,9 +402,11 @@ function editStateName(state) {
// add listeners
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCulture);
byId("stateNameEditorShortRandom").on("click", regenerateShortNameRandom);
byId("stateNameEditorShortAi").on("click", regenerateShortNameAi);
byId("stateNameEditorAddForm").on("click", addCustomForm);
byId("stateNameEditorCustomForm").on("change", addCustomForm);
byId("stateNameEditorFullRegenerate").on("click", regenerateFullName);
byId("stateNameEditorFullAi").on("click", regenerateFullNameAi);

function regenerateShortNameCulture() {
const state = +stateNameEditor.dataset.state;
Expand All @@ -417,6 +421,16 @@ function editStateName(state) {
byId("stateNameEditorShort").value = name;
}

async function regenerateShortNameAi() {
const state = +stateNameEditor.dataset.state;
const culture = pack.states[state].culture;
try {
byId("stateNameEditorShort").value = await AiNames.generateName("state", culture);
} catch (error) {
tip(error.message, true, "error", 4000);
}
}

function addCustomForm() {
const value = stateNameEditorCustomForm.value;
const addModeActive = stateNameEditorCustomForm.style.display === "inline-block";
Expand All @@ -440,6 +454,19 @@ function editStateName(state) {
}
}

async function regenerateFullNameAi() {
const state = +stateNameEditor.dataset.state;
const culture = pack.states[state].culture;
const short = byId("stateNameEditorShort").value;
const form = byId("stateNameEditorSelectForm").value;
try {
const name = await AiNames.generateName("stateFullName", culture, {form: form || "State", stateName: short});
byId("stateNameEditorFull").value = name;
} catch (error) {
tip(error.message, true, "error", 4000);
}
}

function applyNameChange(s) {
const nameInput = byId("stateNameEditorShort");
const formSelect = byId("stateNameEditorSelectForm");
Expand Down Expand Up @@ -1492,6 +1519,43 @@ function downloadStatesCsv() {
downloadFile(csvData, name);
}

async function regenerateStateNamesAi() {
const elements = Array.from($body.querySelectorAll(":scope > div"));
const unlocked = elements.filter(el => {
const id = +el.dataset.id;
return id > 0 && !pack.states[id].lock;
});
if (!unlocked.length) return;

const byCulture = new Map();
for (const el of unlocked) {
const stateId = +el.dataset.id;
const culture = pack.states[stateId].culture;
if (!byCulture.has(culture)) byCulture.set(culture, []);
byCulture.get(culture).push({el, stateId});
}

tip("Generating AI names...", false, "info");

try {
for (const [culture, states] of byCulture) {
const names = await AiNames.generateNames("state", culture, states.length);
for (let i = 0; i < states.length; i++) {
const name = names[i] || Names.getState(Names.getCultureShort(culture), culture);
const {el, stateId} = states[i];
const s = pack.states[stateId];
s.name = el.dataset.name = name;
el.querySelector("input.stateName").value = name;
s.fullName = s.formName ? `${s.formName} of ${name}` : name;
labels.select("[data-id='" + stateId + "']").text(s.fullName);
}
}
tip("AI names generated successfully", true, "success", 3000);
} catch (error) {
tip(error.message, true, "error", 4000);
}
}

function closeStatesEditor() {
if (customization === 2) exitStatesManualAssignment(true);
if (customization === 3) exitAddStateMode();
Expand Down
3 changes: 2 additions & 1 deletion public/modules/ui/ai-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ async function generateWithAnthropic({key, model, prompt, temperature, onContent

async function generateWithOllama({key, model, prompt, temperature, onContent}) {
const ollamaModelName = key; // for Ollama, 'key' is the actual model name entered by the user
const ollamaHost = localStorage.getItem("fmg-ai-ollama-host") || "http://localhost:11434";

const response = await fetch("http://localhost:11434/api/generate", {
const response = await fetch(`${ollamaHost}/api/generate`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
Expand Down
Loading
Loading