Merge branch 'staging' into instruct-rework

This commit is contained in:
Cohee
2024-03-28 01:34:04 +02:00
6 changed files with 43 additions and 15 deletions

View File

@ -4161,7 +4161,7 @@
</div> </div>
<div id="tags_div"> <div id="tags_div">
<div class="tag_controls"> <div class="tag_controls">
<input id="tagInput" class="text_pole tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" /> <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" />
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div> <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
</div> </div>
<div id="tagList" class="tags"></div> <div id="tagList" class="tags"></div>
@ -4181,7 +4181,7 @@
</div> </div>
<div id="descriptionWrapper" class="flex-container flexFlowColumn flex1"> <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
<hr> <hr>
<div id="description_div" class="marginBot5 flex-container alignitemscenter"> <div id="description_div" class="flex-container alignitemscenter">
<span data-i18n="Character Description">Description</span> <span data-i18n="Character Description">Description</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i> <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i>
<a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank"> <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
@ -4194,7 +4194,7 @@
</div> </div>
</div> </div>
<div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1"> <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
<div id="first_message_div" class="marginBot5 title_restorable"> <div id="first_message_div" class="title_restorable">
<div class="flex-container alignitemscenter flex1"> <div class="flex-container alignitemscenter flex1">
<span data-i18n="First message">First message</span> <span data-i18n="First message">First message</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor"></i> <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor"></i>
@ -4244,7 +4244,7 @@
</div> </div>
<div id="group_tags_div" class="wide100p"> <div id="group_tags_div" class="wide100p">
<div class="tag_controls"> <div class="tag_controls">
<input id="groupTagInput" class="text_pole tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" /> <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" />
<div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div> <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
</div> </div>
<div id="groupTagList" class="tags paddingTopBot5"></div> <div id="groupTagList" class="tags paddingTopBot5"></div>

View File

@ -172,6 +172,7 @@ import {
importTags, importTags,
tag_filter_types, tag_filter_types,
compareTagsForSort, compareTagsForSort,
initTags,
} from './scripts/tags.js'; } from './scripts/tags.js';
import { import {
SECRET_KEYS, SECRET_KEYS,
@ -412,6 +413,7 @@ export const event_types = {
CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected', CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected',
// TODO: Naming convention is inconsistent with other events // TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted', CHARACTER_DELETED: 'characterDeleted',
CHARACTER_DUPLICATED: 'character_duplicated',
}; };
export const eventSource = new EventEmitter(); export const eventSource = new EventEmitter();
@ -864,6 +866,7 @@ async function firstLoadInit() {
getSystemMessages(); getSystemMessages();
sendSystemMessage(system_message_types.WELCOME); sendSystemMessage(system_message_types.WELCOME);
initLocales(); initLocales();
initTags();
await getUserAvatars(true, user_avatar); await getUserAvatars(true, user_avatar);
await getCharacters(); await getCharacters();
await getBackgrounds(); await getBackgrounds();
@ -1237,7 +1240,7 @@ function getCharacterBlock(item, id) {
const template = $('#character_template .character_select').clone(); const template = $('#character_template .character_select').clone();
template.attr({ 'chid': id, 'id': `CharID${id}` }); template.attr({ 'chid': id, 'id': `CharID${id}` });
template.find('img').attr('src', this_avatar).attr('alt', item.name); template.find('img').attr('src', this_avatar).attr('alt', item.name);
template.find('.avatar').attr('title', `[Character] ${item.name}`); template.find('.avatar').attr('title', `[Character] ${item.name}\nFile: ${item.avatar}`);
template.find('.ch_name').text(item.name).attr('title', `[Character] ${item.name}`); template.find('.ch_name').text(item.name).attr('title', `[Character] ${item.name}`);
if (power_user.show_card_avatar_urls) { if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar); template.find('.ch_avatar_url').text(item.avatar);
@ -1855,6 +1858,7 @@ function insertSVGIcon(mes, extra) {
function getMessageFromTemplate({ function getMessageFromTemplate({
mesId, mesId,
swipeId,
characterName, characterName,
isUser, isUser,
avatarImg, avatarImg,
@ -1872,6 +1876,7 @@ function getMessageFromTemplate({
const mes = messageTemplate.clone(); const mes = messageTemplate.clone();
mes.attr({ mes.attr({
'mesid': mesId, 'mesid': mesId,
'swipeid': swipeId,
'ch_name': characterName, 'ch_name': characterName,
'is_user': isUser, 'is_user': isUser,
'is_system': !!isSystem, 'is_system': !!isSystem,
@ -2018,6 +2023,7 @@ function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true
let params = { let params = {
mesId: forceId ?? chat.length - 1, mesId: forceId ?? chat.length - 1,
swipeId: mes.swipe_id ?? 0,
characterName: mes.name, characterName: mes.name,
isUser: mes.is_user, isUser: mes.is_user,
avatarImg: avatarImg, avatarImg: avatarImg,
@ -4320,6 +4326,8 @@ async function DupeChar() {
}); });
if (response.ok) { if (response.ok) {
toastr.success('Character Duplicated'); toastr.success('Character Duplicated');
const data = await response.json();
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
getCharacters(); getCharacters();
} }
} }

View File

@ -48,16 +48,24 @@ class CharacterContextMenu {
* Duplicate one or more characters * Duplicate one or more characters
* *
* @param characterId * @param characterId
* @returns {Promise<Response>} * @returns {Promise<any>}
*/ */
static duplicate = async (characterId) => { static duplicate = async (characterId) => {
const character = CharacterContextMenu.#getCharacter(characterId); const character = CharacterContextMenu.#getCharacter(characterId);
const body = { avatar_url: character.avatar };
return fetch('/api/characters/duplicate', { const result = await fetch('/api/characters/duplicate', {
method: 'POST', method: 'POST',
headers: getRequestHeaders(), headers: getRequestHeaders(),
body: JSON.stringify({ avatar_url: character.avatar }), body: JSON.stringify(body),
}); });
if (!result.ok) {
throw new Error('Character not duplicated');
}
const data = await result.json();
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
}; };
/** /**

View File

@ -8,6 +8,8 @@ import {
entitiesFilter, entitiesFilter,
printCharacters, printCharacters,
buildAvatarList, buildAvatarList,
eventSource,
event_types,
} from '../script.js'; } from '../script.js';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js'; import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js';
@ -1151,7 +1153,17 @@ function onClearAllFiltersClick() {
$('#character_search_bar').val('').trigger('input'); $('#character_search_bar').val('').trigger('input');
} }
jQuery(() => { /**
* Copy tags from one character to another.
* @param {{oldAvatar: string, newAvatar: string}} data Event data
*/
function copyTags(data) {
const prevTagMap = tag_map[data.oldAvatar] || [];
const newTagMap = tag_map[data.newAvatar] || [];
tag_map[data.newAvatar] = Array.from(new Set([...prevTagMap, ...newTagMap]));
}
export function initTags() {
createTagInput('#tagInput', '#tagList'); createTagInput('#tagInput', '#tagList');
createTagInput('#groupTagInput', '#groupTagList'); createTagInput('#groupTagInput', '#groupTagList');
@ -1168,5 +1180,5 @@ jQuery(() => {
$(document).on('click', '.tag_view_create', onTagCreateClick); $(document).on('click', '.tag_view_create', onTagCreateClick);
$(document).on('click', '.tag_view_backup', onTagsBackupClick); $(document).on('click', '.tag_view_backup', onTagsBackupClick);
$(document).on('click', '.tag_view_restore', onBackupRestoreClick); $(document).on('click', '.tag_view_restore', onBackupRestoreClick);
}); eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
}

View File

@ -1416,7 +1416,7 @@ input[type="file"] {
} }
.extension_token_counter { .extension_token_counter {
font-size: calc(var(--mainFontSize) * 0.9); font-size: calc(var(--mainFontSize) * 0.875);
width: 100%; width: 100%;
text-align: right; text-align: right;
margin-bottom: 5px; margin-bottom: 5px;

View File

@ -1008,7 +1008,7 @@ router.post('/duplicate', jsonParser, async function (request, response) {
fs.copyFileSync(filename, newFilename); fs.copyFileSync(filename, newFilename);
console.log(`${filename} was copied to ${newFilename}`); console.log(`${filename} was copied to ${newFilename}`);
response.sendStatus(200); response.send({ path: path.parse(newFilename).base });
} }
catch (error) { catch (error) {
console.error(error); console.error(error);