mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator
This commit is contained in:
commit
efbab14d11
5 changed files with 139 additions and 4 deletions
|
|
@ -253,7 +253,7 @@
|
|||
.icon-coa:before {content:'\f3ed'; font-size: .9em; color: #999;} /* '' */
|
||||
.icon-half:before {font-weight: bold;content:'½';}
|
||||
.icon-voice:before {content:'🔊';}
|
||||
|
||||
.icon-robot:before {content:'🤖';}
|
||||
.icon-die:before {content:'🎲';}
|
||||
.icon-button-die:before {content:'🎲'; padding-right: .4em;}
|
||||
.icon-button-power:before {content:'💪'; padding-right: .6em;}
|
||||
|
|
|
|||
30
index.html
30
index.html
|
|
@ -4921,6 +4921,7 @@
|
|||
<div id="notesLegend" contenteditable="true"></div>
|
||||
<div style="margin-top: 0.3em">
|
||||
<button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button>
|
||||
<button id="notesGenerateWithAi" data-tip="Generate note with AI" class="icon-robot"></button>
|
||||
<button
|
||||
id="notesPin"
|
||||
data-tip="Toggle notes box dispay: hide or do not hide the box on mouse move"
|
||||
|
|
@ -4932,6 +4933,32 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="aiGenerator" class="dialog stable" style="display: none">
|
||||
<div style="display: flex; flex-direction: column; gap: 0.3em; width: 100%">
|
||||
<textarea id="aiGeneratorResult" placeholder="Generated text will appear here" cols="30" rows="10"></textarea>
|
||||
<textarea id="aiGeneratorPrompt" placeholder="Type a prompt here" cols="30" rows="5"></textarea>
|
||||
<div style="display: flex; align-items: center; gap: 1em">
|
||||
<label for="aiGeneratorModel"
|
||||
>Model:
|
||||
<select id="aiGeneratorModel"></select>
|
||||
</label>
|
||||
|
||||
<label for="aiGeneratorKey"
|
||||
>Key:
|
||||
<input id="aiGeneratorKey" placeholder="Enter OpenAI API key" class="icon-key" />
|
||||
<a
|
||||
href="https://platform.openai.com/account/api-keys"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="icon-help-circled"
|
||||
style="text-decoration: none"
|
||||
data-tip="Get the key at OpenAI website. The key will be stored in your browser and send to OpenAI API directly. The Map Genenerator doesn't store the key or any generated data"
|
||||
></a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="emblemEditor" class="dialog stable" style="display: none">
|
||||
<svg viewBox="0 0 200 200"><use id="emblemImage"></use></svg>
|
||||
<div id="emblemBody">
|
||||
|
|
@ -8037,7 +8064,8 @@
|
|||
<script defer src="modules/ui/relief-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/burg-editor.js?v=1.99.05"></script>
|
||||
<script defer src="modules/ui/units-editor.js?v=1.99.05"></script>
|
||||
<script defer src="modules/ui/notes-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/notes-editor.js?v=1.99.06"></script>
|
||||
<script defer src="modules/ui/ai-generator.js?v=1.99.06"></script>
|
||||
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/zones-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/burgs-overview.js?v=1.99.05"></script>
|
||||
|
|
|
|||
87
modules/ui/ai-generator.js
Normal file
87
modules/ui/ai-generator.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"use strict";
|
||||
|
||||
const GPT_MODELS = ["gpt-4o-mini", "chatgpt-4o-latest", "gpt-4o", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo"];
|
||||
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
||||
|
||||
function geneateWithAi(defaultPrompt, onApply) {
|
||||
updateValues();
|
||||
|
||||
$("#aiGenerator").dialog({
|
||||
title: "AI Text Generator",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
resizable: false,
|
||||
buttons: {
|
||||
Generate: function (e) {
|
||||
generate(e.target);
|
||||
},
|
||||
Apply: function () {
|
||||
const result = byId("aiGeneratorResult").value;
|
||||
if (!result) return tip("No result to apply", true, "error", 4000);
|
||||
onApply(result);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.geneateWithAi) return;
|
||||
modules.geneateWithAi = true;
|
||||
|
||||
function updateValues() {
|
||||
byId("aiGeneratorResult").value = "";
|
||||
byId("aiGeneratorPrompt").value = defaultPrompt;
|
||||
byId("aiGeneratorKey").value = localStorage.getItem("fmg-ai-kl") || "";
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const model = byId("aiGeneratorModel").value;
|
||||
if (!model) return tip("Please select a model", true, "error", 4000);
|
||||
localStorage.setItem("fmg-ai-model", model);
|
||||
|
||||
const prompt = byId("aiGeneratorPrompt").value;
|
||||
if (!prompt) return tip("Please enter a prompt", true, "error", 4000);
|
||||
|
||||
try {
|
||||
button.disabled = true;
|
||||
byId("aiGeneratorResult").disabled = true;
|
||||
|
||||
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: 1.2
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const json = await response.json();
|
||||
throw new Error(json?.error?.message || "Failed to generate");
|
||||
}
|
||||
|
||||
const {choices} = await response.json();
|
||||
const result = choices[0].message.content;
|
||||
byId("aiGeneratorResult").value = result;
|
||||
} catch (error) {
|
||||
return tip(error.message, true, "error", 4000);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
byId("aiGeneratorResult").disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ function editNotes(id, name) {
|
|||
byId("notesLegend").addEventListener("blur", updateLegend);
|
||||
byId("notesPin").addEventListener("click", toggleNotesPin);
|
||||
byId("notesFocus").addEventListener("click", validateHighlightElement);
|
||||
byId("notesGenerateWithAi").addEventListener("click", openAiGenerator);
|
||||
byId("notesDownload").addEventListener("click", downloadLegends);
|
||||
byId("notesUpload").addEventListener("click", () => legendsToLoad.click());
|
||||
byId("legendsToLoad").addEventListener("change", function () {
|
||||
|
|
@ -143,6 +144,25 @@ function editNotes(id, name) {
|
|||
});
|
||||
}
|
||||
|
||||
function openAiGenerator() {
|
||||
const note = notes.find(note => note.id === notesSelect.value);
|
||||
|
||||
let prompt = `Respond with description. Use simple dry language. Invent facts, names and details. Split to paragraphs and format to HTML. Remove h tags, remove markdown.`;
|
||||
if (note?.name) prompt += ` Name: ${note.name}.`;
|
||||
if (note?.legend) prompt += ` Data: ${note.legend}`;
|
||||
|
||||
const onApply = result => {
|
||||
notesLegend.innerHTML = result;
|
||||
if (note) {
|
||||
note.legend = result;
|
||||
updateNotesBox(note);
|
||||
if (window.tinymce) tinymce.activeEditor.setContent(note.legend);
|
||||
}
|
||||
};
|
||||
|
||||
geneateWithAi(prompt, onApply);
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
const notesData = JSON.stringify(notes);
|
||||
const name = getFileName("Notes") + ".txt";
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ const version = "1.99.07"; // generator version, update each time
|
|||
|
||||
<ul>
|
||||
<strong>Latest changes:</strong>
|
||||
<li>Notes Editor: on-demand AI text generation</li>
|
||||
<li>New style preset: Dark Seas</li>
|
||||
<li>New routes generation algorithm</li>
|
||||
<li>Routes overview tool</li>
|
||||
<li>Configurable longitude</li>
|
||||
|
|
@ -41,8 +43,6 @@ const version = "1.99.07"; // generator version, update each time
|
|||
<li>Auto-load of the last saved map is now optional (see <i>Onload behavior</i> in Options)</li>
|
||||
<li>New label placement algorithm for states</li>
|
||||
<li>North and South Poles temperature can be set independently</li>
|
||||
<li>More than 70 new heraldic charges</li>
|
||||
<li>Multi-color heraldic charges support</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue