mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-19 10:31:24 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into submap-refactoring
This commit is contained in:
commit
b5661de19c
8 changed files with 176 additions and 101 deletions
|
|
@ -972,5 +972,9 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
.attr("letter-spacing", null)
|
||||
.attr("fill", null)
|
||||
.attr("stroke", null);
|
||||
|
||||
// pole can be missing for some states/provinces
|
||||
BurgsAndStates.getPoles();
|
||||
Provinces.getPoles();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,10 +345,13 @@ function cultureChangeName() {
|
|||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
const cultureId = +this.parentNode.dataset.id;
|
||||
const base = pack.cultures[cultureId].base;
|
||||
if (!nameBases[base]) return tip("Namesbase is not defined, please select a valid namesbase", false, "error", 5000);
|
||||
|
||||
const name = Names.getCultureShort(cultureId);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
pack.cultures[cultureId].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
|
|
@ -494,12 +497,15 @@ function cultureRegenerateBurgs() {
|
|||
if (customization === 4) return;
|
||||
|
||||
const cultureId = +this.parentNode.dataset.id;
|
||||
const cBurgs = pack.burgs.filter(b => b.culture === cultureId && !b.lock);
|
||||
cBurgs.forEach(b => {
|
||||
const base = pack.cultures[cultureId].base;
|
||||
if (!nameBases[base]) return tip("Namesbase is not defined, please select a valid namesbase", false, "error", 5000);
|
||||
|
||||
const cultureBurgs = pack.burgs.filter(b => b.culture === cultureId && !b.removed && !b.lock);
|
||||
cultureBurgs.forEach(b => {
|
||||
b.name = Names.getCulture(cultureId);
|
||||
labels.select("[data-id='" + b.i + "']").text(b.name);
|
||||
});
|
||||
tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success");
|
||||
tip(`Names for ${cultureBurgs.length} burgs are regenerated`, false, "success");
|
||||
}
|
||||
|
||||
function removeCulture(cultureId) {
|
||||
|
|
@ -849,14 +855,15 @@ async function uploadCulturesData() {
|
|||
this.value = "";
|
||||
const csv = await file.text();
|
||||
const data = d3.csvParse(csv, d => ({
|
||||
i: +d.Id,
|
||||
name: d.Name,
|
||||
i: +d.Id,
|
||||
color: d.Color,
|
||||
expansionism: +d.Expansionism,
|
||||
type: d.Type,
|
||||
population: +d.Population,
|
||||
emblemsShape: d["Emblems Shape"],
|
||||
origins: d.Origins
|
||||
origins: d.Origins,
|
||||
namesbase: d.Namesbase
|
||||
}));
|
||||
|
||||
const {cultures, cells} = pack;
|
||||
|
|
@ -883,7 +890,7 @@ async function uploadCulturesData() {
|
|||
culture.i
|
||||
);
|
||||
} else {
|
||||
current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origin: 0, rural: 0, urban: 0};
|
||||
current = {i: cultures.length, center: ra(populated), area: 0, cells: 0, origins: [0], rural: 0, urban: 0};
|
||||
cultures.push(current);
|
||||
}
|
||||
|
||||
|
|
@ -903,6 +910,10 @@ async function uploadCulturesData() {
|
|||
else current.type = "Generic";
|
||||
}
|
||||
|
||||
culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null];
|
||||
current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater";
|
||||
current.base = nameBases.findIndex(n => n.name == culture.namesbase); // can be -1 if namesbase is not found
|
||||
|
||||
function restoreOrigins(originsString) {
|
||||
const originNames = originsString
|
||||
.replaceAll('"', "")
|
||||
|
|
@ -918,12 +929,6 @@ async function uploadCulturesData() {
|
|||
current.origins = originIds.filter(id => id !== null);
|
||||
if (!current.origins.length) current.origins = [0];
|
||||
}
|
||||
|
||||
culture.origins = current.i ? restoreOrigins(culture.origins || "") : [null];
|
||||
current.shield = shapes.includes(culture.emblemsShape) ? culture.emblemsShape : "heater";
|
||||
|
||||
const nameBaseIndex = nameBases.findIndex(n => n.name == culture.namesbase);
|
||||
current.base = nameBaseIndex === -1 ? 0 : nameBaseIndex;
|
||||
}
|
||||
|
||||
cultures.filter(c => c.removed).forEach(c => removeCulture(c.i));
|
||||
|
|
|
|||
|
|
@ -583,4 +583,9 @@ James Benware
|
|||
FortunesFaded
|
||||
breadsticks
|
||||
Murderbits
|
||||
Ben Jones`;
|
||||
Ben Jones
|
||||
Marco Faltracco
|
||||
L
|
||||
silentArtifact
|
||||
Keith Potter
|
||||
Morgan Gilbert`;
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ async function parseLoadedData(data, mapVersion) {
|
|||
|
||||
{
|
||||
// dynamically import and run auto-update script
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.10");
|
||||
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.105.24");
|
||||
resolveVersionConflicts(mapVersion);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,114 @@
|
|||
"use strict";
|
||||
|
||||
const GPT_MODELS = ["gpt-4o-mini", "chatgpt-4o-latest", "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
|
||||
const PROVIDERS = {
|
||||
openai: {
|
||||
keyLink: "https://platform.openai.com/account/api-keys",
|
||||
generate: generateWithOpenAI
|
||||
},
|
||||
anthropic: {
|
||||
keyLink: "https://console.anthropic.com/account/keys",
|
||||
generate: generateWithAnthropic
|
||||
}
|
||||
};
|
||||
|
||||
const DEFAULT_MODEL = "gpt-4o-mini";
|
||||
|
||||
const MODELS = {
|
||||
"gpt-4o-mini": "openai",
|
||||
"chatgpt-4o-latest": "openai",
|
||||
"gpt-4o": "openai",
|
||||
"gpt-4-turbo": "openai",
|
||||
"o1-preview": "openai",
|
||||
"o1-mini": "openai",
|
||||
"claude-3-5-haiku-latest": "anthropic",
|
||||
"claude-3-5-sonnet-latest": "anthropic",
|
||||
"claude-3-opus-latest": "anthropic"
|
||||
};
|
||||
|
||||
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
||||
|
||||
async function generateWithOpenAI({key, model, prompt, temperature, onContent}) {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key}`
|
||||
};
|
||||
|
||||
const messages = [
|
||||
{role: "system", content: SYSTEM_MESSAGE},
|
||||
{role: "user", content: prompt}
|
||||
];
|
||||
|
||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({model, messages, temperature, stream: true})
|
||||
});
|
||||
|
||||
const getContent = json => {
|
||||
const content = json.choices?.[0]?.delta?.content;
|
||||
if (content) onContent(content);
|
||||
};
|
||||
|
||||
await handleStream(response, getContent);
|
||||
}
|
||||
|
||||
async function generateWithAnthropic({key, model, prompt, temperature, onContent}) {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-dangerous-direct-browser-access": "true"
|
||||
};
|
||||
|
||||
const messages = [{role: "user", content: prompt}];
|
||||
|
||||
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({model, system: SYSTEM_MESSAGE, messages, temperature, max_tokens: 4096, stream: true})
|
||||
});
|
||||
|
||||
const getContent = json => {
|
||||
const content = json.delta?.text;
|
||||
if (content) onContent(content);
|
||||
};
|
||||
|
||||
await handleStream(response, getContent);
|
||||
}
|
||||
|
||||
async function handleStream(response, getContent) {
|
||||
if (!response.ok) {
|
||||
const json = await response.json();
|
||||
throw new Error(json?.error?.message || "Failed to generate");
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, {stream: true});
|
||||
const lines = buffer.split("\n");
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
||||
try {
|
||||
const json = JSON.parse(line.slice(6));
|
||||
getContent(json);
|
||||
} catch (jsonError) {
|
||||
ERROR && console.error(`Failed to parse JSON:`, jsonError, `Line: ${line}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer = lines.at(-1);
|
||||
}
|
||||
}
|
||||
|
||||
function generateWithAi(defaultPrompt, onApply) {
|
||||
updateValues();
|
||||
|
||||
|
|
@ -29,90 +135,53 @@ function generateWithAi(defaultPrompt, onApply) {
|
|||
if (modules.generateWithAi) return;
|
||||
modules.generateWithAi = true;
|
||||
|
||||
byId("aiGeneratorKeyHelp").on("click", function (e) {
|
||||
const model = byId("aiGeneratorModel").value;
|
||||
const provider = MODELS[model];
|
||||
openURL(PROVIDERS[provider].keyLink);
|
||||
});
|
||||
|
||||
function updateValues() {
|
||||
byId("aiGeneratorResult").value = "";
|
||||
byId("aiGeneratorPrompt").value = defaultPrompt;
|
||||
byId("aiGeneratorKey").value = localStorage.getItem("fmg-ai-kl") || "";
|
||||
byId("aiGeneratorTemperature").value = localStorage.getItem("fmg-ai-temperature") || "1.2";
|
||||
byId("aiGeneratorTemperature").value = localStorage.getItem("fmg-ai-temperature") || "1";
|
||||
|
||||
const select = byId("aiGeneratorModel");
|
||||
select.options.length = 0;
|
||||
GPT_MODELS.forEach(model => select.options.add(new Option(model, model)));
|
||||
select.value = localStorage.getItem("fmg-ai-model") || GPT_MODELS[0];
|
||||
Object.keys(MODELS).forEach(model => select.options.add(new Option(model, model)));
|
||||
select.value = localStorage.getItem("fmg-ai-model");
|
||||
if (!select.value || !MODELS[select.value]) select.value = DEFAULT_MODEL;
|
||||
|
||||
const provider = MODELS[select.value];
|
||||
byId("aiGeneratorKey").value = localStorage.getItem(`fmg-ai-kl-${provider}`) || "";
|
||||
}
|
||||
|
||||
async function generate(button) {
|
||||
const key = byId("aiGeneratorKey").value;
|
||||
if (!key) return tip("Please enter an OpenAI API key", true, "error", 4000);
|
||||
localStorage.setItem("fmg-ai-kl", key);
|
||||
if (!key) return tip("Please enter an API key", true, "error", 4000);
|
||||
|
||||
const model = byId("aiGeneratorModel").value;
|
||||
if (!model) return tip("Please select a model", true, "error", 4000);
|
||||
localStorage.setItem("fmg-ai-model", model);
|
||||
|
||||
const provider = MODELS[model];
|
||||
localStorage.setItem(`fmg-ai-kl-${provider}`, key);
|
||||
|
||||
const prompt = byId("aiGeneratorPrompt").value;
|
||||
if (!prompt) return tip("Please enter a prompt", true, "error", 4000);
|
||||
|
||||
const temperature = parseFloat(byId("aiGeneratorTemperature").value);
|
||||
if (isNaN(temperature) || temperature < 0 || temperature > 2) {
|
||||
return tip("Temperature must be a number between 0 and 2", true, "error", 4000);
|
||||
}
|
||||
localStorage.setItem("fmg-ai-temperature", temperature.toString());
|
||||
const temperature = byId("aiGeneratorTemperature").valueAsNumber;
|
||||
if (isNaN(temperature)) return tip("Temperature must be a number", true, "error", 4000);
|
||||
localStorage.setItem("fmg-ai-temperature", temperature);
|
||||
|
||||
try {
|
||||
button.disabled = true;
|
||||
const resultArea = byId("aiGeneratorResult");
|
||||
resultArea.value = "";
|
||||
resultArea.disabled = true;
|
||||
resultArea.value = "";
|
||||
const onContent = content => (resultArea.value += content);
|
||||
|
||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [
|
||||
{role: "system", content: SYSTEM_MESSAGE},
|
||||
{role: "user", content: prompt}
|
||||
],
|
||||
temperature: temperature,
|
||||
stream: true // Enable streaming
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const json = await response.json();
|
||||
throw new Error(json?.error?.message || "Failed to generate");
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, {stream: true});
|
||||
const lines = buffer.split("\n");
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
||||
try {
|
||||
const jsonData = JSON.parse(line.slice(6));
|
||||
const content = jsonData.choices[0].delta.content;
|
||||
if (content) resultArea.value += content;
|
||||
} catch (jsonError) {
|
||||
console.warn("Failed to parse JSON:", jsonError, "Line:", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer = lines[lines.length - 1];
|
||||
}
|
||||
await PROVIDERS[provider].generate({key, model, prompt, temperature, onContent});
|
||||
} catch (error) {
|
||||
return tip(error.message, true, "error", 4000);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1255,7 +1255,7 @@ async function editStates() {
|
|||
|
||||
async function editCultures() {
|
||||
if (customization) return;
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.105.11");
|
||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.105.23");
|
||||
Editor.open();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue