Skip to content

Commit 759ea68

Browse files
author
prima
committed
Merge remote-tracking branch 'origin/characterCreator' into remoteManagement
2 parents e645f26 + f94fb8e commit 759ea68

File tree

1 file changed

+288
-1
lines changed

1 file changed

+288
-1
lines changed

klite.embd

Lines changed: 288 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33622,6 +33622,7 @@ class MarkdownWYSIWYG {
3362233622
}
3362333623
}, 1000);
3362433624
}
33625+
3362533626
</script>
3362633627
<script>
3362733628
/**
@@ -37579,6 +37580,24 @@ flowchart TD\n${treeToViewOutput.outputText.trim()}`
3757937580
width: unset;
3758037581
}
3758137582

37583+
/* Ensure form fields are readable inside popups */
37584+
#popupContainer .popupContent input[type="text"],
37585+
#popupContainer .popupContent input[type="file"],
37586+
#popupContainer .popupContent textarea,
37587+
#popupContainer .popupContent select {
37588+
color: #000;
37589+
background-color: #fff;
37590+
border: 1px solid var(--colgrey, #ccc);
37591+
border-radius: 6px;
37592+
padding: 6px 8px;
37593+
box-sizing: border-box;
37594+
}
37595+
37596+
#popupContainer .popupContent input::placeholder,
37597+
#popupContainer .popupContent textarea::placeholder {
37598+
color: #666;
37599+
}
37600+
3758237601
.containAndScaleImage.tile
3758337602
{
3758437603
height: 128px;
@@ -37615,6 +37634,21 @@ flowchart TD\n${treeToViewOutput.outputText.trim()}`
3761537634
{
3761637635
display: block;
3761737636
}
37637+
37638+
.characterCreatorGrid {
37639+
color: white;
37640+
padding: 10px;
37641+
display: grid;
37642+
grid-template-columns: 1fr 1fr;
37643+
gap: 8px;
37644+
min-width: 600px;
37645+
}
37646+
37647+
@media (max-width: 800px) {
37648+
.characterCreatorGrid {
37649+
grid-template-columns: 1fr;
37650+
}
37651+
}
3761837652
</style>
3761937653
<script>
3762037654
class PopupUtils
@@ -37893,7 +37927,9 @@ flowchart TD\n${treeToViewOutput.outputText.trim()}`
3789337927
}
3789437928
popupUtils.reset().title(`Character List (${allCharacterNames.length})`)
3789537929
containers.forEach(container => popupUtils.content(container))
37896-
popupUtils.button("Upload characters, lorebooks and world info", () => {
37930+
popupUtils
37931+
.button("New character", () => { try { showCharacterCreator(); } catch(e){ console.error(e); } })
37932+
.button("Upload characters, lorebooks and world info", () => {
3789737933
popupUtils.reset()
3789837934
promptUserForLocalFile(async (result) => {
3789937935
let { file, fileName, ext, content, plaintext, dataArr } = result;
@@ -37964,5 +38000,256 @@ flowchart TD\n${treeToViewOutput.outputText.trim()}`
3796438000
waitingToast.hide()
3796538001
}).button("Close", () => popupUtils.reset()).show();
3796638002
}
38003+
38004+
// Native character creator popup for esobold
38005+
function showCharacterCreator() {
38006+
const form = document.createElement('div');
38007+
form.classList.add("characterCreatorGrid")
38008+
38009+
const makeLabel = (text) => {
38010+
const l = document.createElement('label');
38011+
l.innerText = text;
38012+
return l;
38013+
};
38014+
const makeInput = (type='text') => {
38015+
const i = document.createElement('input');
38016+
i.type = type;
38017+
i.classList.add('textbox');
38018+
return i;
38019+
};
38020+
const makeArea = () => {
38021+
const a = document.createElement('textarea');
38022+
a.classList.add('textbox');
38023+
a.rows = 4;
38024+
return a;
38025+
};
38026+
const addOption = (select, value, text) => {
38027+
const o = document.createElement('option');
38028+
o.value = value;
38029+
o.text = text;
38030+
select.appendChild(o);
38031+
};
38032+
38033+
const nameInp = makeInput();
38034+
const creatorInp = makeInput();
38035+
const versionInp = makeInput();
38036+
versionInp.placeholder = '1.0';
38037+
const tagsInp = makeInput();
38038+
tagsInp.placeholder = 'comma,separated,tags';
38039+
const personalityArea = makeArea();
38040+
const descriptionArea = makeArea();
38041+
const mesExampleArea = makeArea();
38042+
const firstMesArea = makeArea();
38043+
firstMesArea.rows = 3;
38044+
const altGreetingsArea = makeArea();
38045+
altGreetingsArea.placeholder = 'One greeting per line';
38046+
const creatorNotesArea = makeArea();
38047+
const systemPromptArea = makeArea();
38048+
const postHistoryArea = makeArea();
38049+
38050+
// World Info group selector
38051+
const wiGroupSelect = document.createElement('select');
38052+
wiGroupSelect.classList.add('textbox');
38053+
const wiCustomGroup = makeInput();
38054+
wiCustomGroup.placeholder = 'Or enter custom WI group (optional)';
38055+
addOption(wiGroupSelect, '', '— No WI group —');
38056+
try {
38057+
const potentialWIGroups = (current_wi || []).map(w => {
38058+
return (w?.wigroup || '').trim()
38059+
}).filter(x => x.length > 0);
38060+
const distinctWIGroups = [...new Set(potentialWIGroups)].sort();
38061+
distinctWIGroups.forEach(g => addOption(wiGroupSelect, g, g));
38062+
}
38063+
catch(e) {
38064+
handleError(e)
38065+
}
38066+
38067+
// Image upload and preview
38068+
let imageFile = null, imagePreview = document.createElement('img');
38069+
imagePreview.style.maxHeight = '120px';
38070+
imagePreview.style.borderRadius = '8px';
38071+
imagePreview.style.display = 'none';
38072+
const imageInp = makeInput('file');
38073+
imageInp.accept = 'image/*';
38074+
imageInp.onchange = () => {
38075+
const f = imageInp.files?.[0];
38076+
if (!f) {
38077+
return;
38078+
}
38079+
imageFile = f;
38080+
try {
38081+
const url = URL.createObjectURL(f);
38082+
imagePreview.src = url;
38083+
imagePreview.style.display = '';
38084+
// best-effort revoke after load
38085+
imagePreview.onload = () => {
38086+
try {
38087+
URL.revokeObjectURL(url);
38088+
}
38089+
catch(e) {
38090+
handleError(e)
38091+
}
38092+
};
38093+
}
38094+
catch(e) {
38095+
handleError(e)
38096+
}
38097+
};
38098+
38099+
// Layout: two columns
38100+
const col = () => {
38101+
const d=document.createElement('div');
38102+
d.style.display='flex';
38103+
d.style.flexDirection='column';
38104+
d.style.gap='8px';
38105+
return d;
38106+
};
38107+
const leftCol = col();
38108+
const rightCol = col();
38109+
const add = (container, label, control, extra=null) => {
38110+
container.append(label, control);
38111+
if(extra) {
38112+
container.append(extra);
38113+
}
38114+
};
38115+
38116+
// Left (primary)
38117+
add(leftCol, makeLabel('Name'), nameInp);
38118+
add(leftCol, makeLabel('Creator'), creatorInp);
38119+
add(leftCol, makeLabel('Version'), versionInp);
38120+
add(leftCol, makeLabel('Tags'), tagsInp);
38121+
add(leftCol, makeLabel('Personality'), personalityArea);
38122+
add(leftCol, makeLabel('Memory / Description'), descriptionArea);
38123+
add(leftCol, makeLabel('First message'), firstMesArea);
38124+
38125+
// Right (secondary)
38126+
add(rightCol, makeLabel('Example dialogue'), mesExampleArea);
38127+
add(rightCol, makeLabel('Alternate greetings (one per line)'), altGreetingsArea);
38128+
add(rightCol, makeLabel('Creator notes'), creatorNotesArea);
38129+
add(rightCol, makeLabel('System prompt'), systemPromptArea);
38130+
add(rightCol, makeLabel('Post history instructions'), postHistoryArea);
38131+
add(rightCol, makeLabel('World Info group'), wiGroupSelect, wiCustomGroup);
38132+
add(rightCol, makeLabel('Avatar image'), imageInp, imagePreview);
38133+
38134+
form.append(leftCol, rightCol);
38135+
38136+
const doSave = async () => {
38137+
const name = (nameInp.value||'').trim();
38138+
if (!name) {
38139+
alert('Character must have a name.');
38140+
return;
38141+
}
38142+
if (!imageFile) {
38143+
alert('Please choose an avatar image.');
38144+
return;
38145+
}
38146+
38147+
// Confirm overwrite if needed
38148+
const exists = (allCharacterNames || []).some(c => c?.name === name);
38149+
if (exists && !confirm(`Character "${name}" exists. Overwrite?`)) {
38150+
return;
38151+
}
38152+
38153+
// Build Tavern v2-compatible inner data object (esobold stores inner fields)
38154+
const tags = (tagsInp.value||'')
38155+
.split(',')
38156+
.map(s => s.trim())
38157+
.filter(s => s.length>0);
38158+
const altGreetings = (altGreetingsArea.value||'')
38159+
.split(/\r?\n/)
38160+
.map(s => s.trim())
38161+
.filter(s => s.length>0);
38162+
38163+
const selectedGroup = (wiCustomGroup.value||'').trim() || wiGroupSelect.value;
38164+
let character_book = null;
38165+
if (selectedGroup) {
38166+
try {
38167+
const entries = (current_wi||[]).filter(e => (e?.wigroup||'') === selectedGroup);
38168+
let id = 0, convertedEntries = entries.map(entry => {
38169+
let convertedEntry = Object.assign(entry, {
38170+
keys: entry?.key?.split(",").filter(k => k !== "") || [],
38171+
secondary_keys: entry?.keysecondary?.split(",").filter(k => k !== "") || [],
38172+
uid: id++
38173+
});
38174+
delete convertedEntry?.key;
38175+
delete convertedEntry?.keysecondary;
38176+
delete convertedEntry?.wigroup;
38177+
return convertedEntry;
38178+
})
38179+
character_book = { name: selectedGroup, entries: convertedEntries };
38180+
}
38181+
catch(e) {
38182+
handleError(e)
38183+
}
38184+
}
38185+
38186+
const charInner = {
38187+
name,
38188+
description: descriptionArea.value||'',
38189+
personality: personalityArea.value||'',
38190+
mes_example: mesExampleArea.value||'',
38191+
first_mes: firstMesArea.value||'',
38192+
creator: creatorInp.value||'',
38193+
creator_notes: creatorNotesArea.value||'',
38194+
system_prompt: systemPromptArea.value||'',
38195+
post_history_instructions: postHistoryArea.value||'',
38196+
alternate_greetings: altGreetings,
38197+
character_book,
38198+
tags,
38199+
character_version: (versionInp.value||'1.0')
38200+
};
38201+
38202+
try {
38203+
waitingToast.setText(`Saving character ${name}`);
38204+
waitingToast.show();
38205+
38206+
// Thumbnail and full image
38207+
const thumbUrl = await generateThumbnail(imageFile, [256,256]);
38208+
const dataUrl = await new Promise((resolve, reject) => {
38209+
const fr = new FileReader();
38210+
fr.onerror = reject;
38211+
fr.onload = () => resolve(fr.result);
38212+
fr.readAsDataURL(imageFile);
38213+
});
38214+
38215+
const toSave = { name, data: charInner, image: String(dataUrl) };
38216+
await indexeddb_save(`character_${name}`, JSON.stringify(toSave));
38217+
38218+
// Update list
38219+
allCharacterNames = (allCharacterNames||[]).filter(c => c?.name !== name);
38220+
allCharacterNames.push({ name, thumbnail: thumbUrl, type: 'Character' });
38221+
await updateCharacterListFromAll();
38222+
38223+
waitingToast.hide();
38224+
popupUtils.reset();
38225+
showCharacterList();
38226+
}
38227+
catch (e) {
38228+
handleError(e);
38229+
waitingToast.hide();
38230+
}
38231+
};
38232+
38233+
popupUtils.reset()
38234+
.title('New Character')
38235+
.content(form)
38236+
.button('Back', showCharacterList)
38237+
.button('Save', doSave)
38238+
.button('Close', () => popupUtils.reset())
38239+
.show();
38240+
}
38241+
38242+
// Backwards-compat for existing button in scenarios
38243+
function character_creator() {
38244+
try
38245+
{
38246+
hide_popups();
38247+
showCharacterCreator();
38248+
}
38249+
catch(e)
38250+
{
38251+
handleError(e);
38252+
}
38253+
}
3796738254
</script>
3796838255
</html>

0 commit comments

Comments
 (0)