Permanent assistant autocreation and temporary chat restore
| @@ -282,7 +282,7 @@ import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; | |||
| 282 | import { getContext } from './scripts/st-context.js'; | 282 | import { getContext } from './scripts/st-context.js'; |
| 283 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; | 283 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; |
| 284 | import { accountStorage } from './scripts/util/AccountStorage.js'; | 284 | import { accountStorage } from './scripts/util/AccountStorage.js'; |
| 285 | import { initWelcomeScreen } from './scripts/welcome-screen.js'; | 285 | import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard } from './scripts/welcome-screen.js'; |
| 286 | 286 | ||
| 287 | // API OBJECT FOR EXTERNAL WIRING | 287 | // API OBJECT FOR EXTERNAL WIRING |
| 288 | globalThis.SillyTavern = { | 288 | globalThis.SillyTavern = { |
| @@ -2041,7 +2041,7 @@ export async function sendTextareaMessage() { | |||
| 2041 | } | 2041 | } |
| 2042 | 2042 | ||
| 2043 | if (textareaText && !selected_group && this_chid === undefined && name2 !== neutralCharacterName) { | 2043 | if (textareaText && !selected_group && this_chid === undefined && name2 !== neutralCharacterName) { |
| 2044 | await newAssistantChat(); | 2044 | await newAssistantChat({ temporary: false }); |
| 2045 | } | 2045 | } |
| 2046 | 2046 | ||
| 2047 | Generate(generateType); | 2047 | Generate(generateType); |
| @@ -2883,7 +2883,14 @@ export async function processCommands(message) { | |||
| 2883 | return true; | 2883 | return true; |
| 2884 | } | 2884 | } |
| 2885 | 2885 | ||
| 2886 | export function sendSystemMessage(type, text, extra = {}) { | 2886 | /** |
| 2887 | * Gets a system message by type. | ||
| 2888 | * @param {string} type Type of system message | ||
| 2889 | * @param {string} [text] Text to be sent | ||
| 2890 | * @param {object} [extra] Additional data to be added to the message | ||
| 2891 | * @returns {object} System message object | ||
| 2892 | */ | ||
| 2893 | export function getSystemMessageByType(type, text, extra = {}) { | ||
| 2887 | const systemMessage = system_messages[type]; | 2894 | const systemMessage = system_messages[type]; |
| 2888 | 2895 | ||
| 2889 | if (!systemMessage) { | 2896 | if (!systemMessage) { |
| @@ -2906,7 +2913,17 @@ export function sendSystemMessage(type, text, extra = {}) { | |||
| 2906 | 2913 | ||
| 2907 | newMessage.extra = Object.assign(newMessage.extra, extra); | 2914 | newMessage.extra = Object.assign(newMessage.extra, extra); |
| 2908 | newMessage.extra.type = type; | 2915 | newMessage.extra.type = type; |
| 2916 | return newMessage; | ||
| 2917 | } | ||
| 2909 | 2918 | ||
| 2919 | /** | ||
| 2920 | * Sends a system message to the chat. | ||
| 2921 | * @param {string} type Type of system message | ||
| 2922 | * @param {string} [text] Text to be sent | ||
| 2923 | * @param {object} [extra] Additional data to be added to the message | ||
| 2924 | */ | ||
| 2925 | export function sendSystemMessage(type, text, extra = {}) { | ||
| 2926 | const newMessage = getSystemMessageByType(type, text, extra); | ||
| 2910 | chat.push(newMessage); | 2927 | chat.push(newMessage); |
| 2911 | addOneMessage(newMessage); | 2928 | addOneMessage(newMessage); |
| 2912 | is_send_press = false; | 2929 | is_send_press = false; |
| @@ -10113,8 +10130,17 @@ async function removeCharacterFromUI() { | |||
| 10113 | saveSettingsDebounced(); | 10130 | saveSettingsDebounced(); |
| 10114 | } | 10131 | } |
| 10115 | 10132 | ||
| 10116 | export async function newAssistantChat() { | 10133 | /** |
| 10134 | * Creates a new assistant chat. | ||
| 10135 | * @param {object} params - Parameters for the new assistant chat | ||
| 10136 | * @param {boolean} [params.temporary=false] I need a temporary secretary | ||
| 10137 | * @returns {Promise<void>} - A promise that resolves when the new assistant chat is created | ||
| 10138 | */ | ||
| 10139 | export async function newAssistantChat({ temporary = false } = {}) { | ||
| 10117 | await clearChat(); | 10140 | await clearChat(); |
| 10141 | if (!temporary) { | ||
| 10142 | return openPermanentAssistantChat(); | ||
| 10143 | } | ||
| 10118 | chat.splice(0, chat.length); | 10144 | chat.splice(0, chat.length); |
| 10119 | chat_metadata = {}; | 10145 | chat_metadata = {}; |
| 10120 | setCharacterName(neutralCharacterName); | 10146 | setCharacterName(neutralCharacterName); |
| @@ -10427,7 +10453,7 @@ jQuery(async function () { | |||
| 10427 | if (chatId) { | 10453 | if (chatId) { |
| 10428 | return reject('Not in a temporary chat'); | 10454 | return reject('Not in a temporary chat'); |
| 10429 | } | 10455 | } |
| 10430 | await newAssistantChat(); | 10456 | await newAssistantChat({ temporary: true }); |
| 10431 | return resolve(''); | 10457 | return resolve(''); |
| 10432 | }; | 10458 | }; |
| 10433 | eventSource.once(event_types.CHAT_CHANGED, eventCallback); | 10459 | eventSource.once(event_types.CHAT_CHANGED, eventCallback); |
| @@ -11094,6 +11120,9 @@ jQuery(async function () { | |||
| 11094 | }); | 11120 | }); |
| 11095 | 11121 | ||
| 11096 | if (id == 'option_select_chat') { | 11122 | if (id == 'option_select_chat') { |
| 11123 | if (this_chid === undefined && !is_send_press) { | ||
| 11124 | await openPermanentAssistantCard(); | ||
| 11125 | } | ||
| 11097 | if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) { | 11126 | if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) { |
| 11098 | await displayPastChats(); | 11127 | await displayPastChats(); |
| 11099 | //this is just to avoid the shadow for past chat view when using /delchat | 11128 | //this is just to avoid the shadow for past chat view when using /delchat |
| @@ -11124,7 +11153,7 @@ jQuery(async function () { | |||
| 11124 | await doNewChat({ deleteCurrentChat: deleteCurrentChat }); | 11153 | await doNewChat({ deleteCurrentChat: deleteCurrentChat }); |
| 11125 | } | 11154 | } |
| 11126 | if (!selected_group && this_chid === undefined && !is_send_press) { | 11155 | if (!selected_group && this_chid === undefined && !is_send_press) { |
| 11127 | await newAssistantChat(); | 11156 | await newAssistantChat({ temporary: true }); |
| 11128 | } | 11157 | } |
| 11129 | } | 11158 | } |
| 11130 | 11159 | ||
| @@ -23,6 +23,9 @@ import { | |||
| 23 | neutralCharacterName, | 23 | neutralCharacterName, |
| 24 | updateChatMetadata, | 24 | updateChatMetadata, |
| 25 | system_message_types, | 25 | system_message_types, |
| 26 | getSystemMessageByType, | ||
| 27 | printMessages, | ||
| 28 | clearChat, | ||
| 26 | } from '../script.js'; | 29 | } from '../script.js'; |
| 27 | import { selected_group } from './group-chats.js'; | 30 | import { selected_group } from './group-chats.js'; |
| 28 | import { power_user } from './power-user.js'; | 31 | import { power_user } from './power-user.js'; |
| @@ -37,6 +40,7 @@ import { | |||
| 37 | saveBase64AsFile, | 40 | saveBase64AsFile, |
| 38 | extractTextFromOffice, | 41 | extractTextFromOffice, |
| 39 | download, | 42 | download, |
| 43 | getFileText, | ||
| 40 | } from './utils.js'; | 44 | } from './utils.js'; |
| 41 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; | 45 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; |
| 42 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 46 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| @@ -1497,6 +1501,35 @@ jQuery(function () { | |||
| 1497 | download(chatToSave.map((m) => JSON.stringify(m)).join('\n'), `Assistant - ${humanizedDateTime()}.jsonl`, 'application/json'); | 1501 | download(chatToSave.map((m) => JSON.stringify(m)).join('\n'), `Assistant - ${humanizedDateTime()}.jsonl`, 'application/json'); |
| 1498 | }); | 1502 | }); |
| 1499 | 1503 | ||
| 1504 | $(document).on('click', '.assistant_note_import', async function () { | ||
| 1505 | const importFile = async () => { | ||
| 1506 | const file = fileInput.files[0]; | ||
| 1507 | if (!file) { | ||
| 1508 | return; | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | try { | ||
| 1512 | const text = await getFileText(file); | ||
| 1513 | const lines = text.split('\n').filter(line => line.trim() !== ''); | ||
| 1514 | const messages = lines.map(line => JSON.parse(line)); | ||
| 1515 | const metadata = messages.shift()?.chat_metadata || {}; | ||
| 1516 | messages.unshift(getSystemMessageByType(system_message_types.ASSISTANT_NOTE)); | ||
| 1517 | await clearChat(); | ||
| 1518 | chat.splice(0, chat.length, ...messages); | ||
| 1519 | updateChatMetadata(metadata, true); | ||
| 1520 | await printMessages(); | ||
| 1521 | } catch (error) { | ||
| 1522 | console.error('Error importing assistant chat:', error); | ||
| 1523 | toastr.error(t`It's either corrupted or not a valid JSONL file.`, t`Failed to import chat`); | ||
| 1524 | } | ||
| 1525 | }; | ||
| 1526 | const fileInput = document.createElement('input'); | ||
| 1527 | fileInput.type = 'file'; | ||
| 1528 | fileInput.accept = '.jsonl'; | ||
| 1529 | fileInput.addEventListener('change', importFile); | ||
| 1530 | fileInput.click(); | ||
| 1531 | }); | ||
| 1532 | |||
| 1500 | // Do not change. #attachFile is added by extension. | 1533 | // Do not change. #attachFile is added by extension. |
| 1501 | $(document).on('click', '#attachFile', function () { | 1534 | $(document).on('click', '#attachFile', function () { |
| 1502 | $('#file_form_input').trigger('click'); | 1535 | $('#file_form_input').trigger('click'); |
| @@ -1,9 +1,13 @@ | |||
| 1 | <div data-type="assistant_note"> | 1 | <div data-type="assistant_note"> |
| 2 | <div> | 2 | <div class="assistant_note_title"> |
| 3 | <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span> | 3 | <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span> |
| 4 | <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span> | ||
| 5 | </div> | 4 | </div> |
| 6 | <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL"> | 5 | <button class="assistant_note_import menu_button menu_button_icon margin0" data-i18n="[title]Import from JSONL" title="Import from JSONL"> |
| 6 | <i class="fa-solid fa-file-import"></i> | ||
| 7 | <span data-i18n="Load">Load</span> | ||
| 8 | </button> | ||
| 9 | <button class="assistant_note_export menu_button menu_button_icon margin0" data-i18n="[title]Export as JSONL" title="Export as JSONL"> | ||
| 7 | <i class="fa-solid fa-file-export"></i> | 10 | <i class="fa-solid fa-file-export"></i> |
| 8 | </div> | 11 | <span data-i18n="Save">Save</span> |
| 12 | </button> | ||
| 9 | </div> | 13 | </div> |
| @@ -1,21 +1,28 @@ | |||
| 1 | import { | 1 | import { |
| 2 | characters, | 2 | characters, |
| 3 | displayVersion, | 3 | displayVersion, |
| 4 | doNewChat, | ||
| 4 | event_types, | 5 | event_types, |
| 5 | eventSource, | 6 | eventSource, |
| 7 | getCharacters, | ||
| 6 | getCurrentChatId, | 8 | getCurrentChatId, |
| 7 | getRequestHeaders, | 9 | getRequestHeaders, |
| 8 | getThumbnailUrl, | 10 | getThumbnailUrl, |
| 11 | is_send_press, | ||
| 12 | neutralCharacterName, | ||
| 9 | newAssistantChat, | 13 | newAssistantChat, |
| 10 | openCharacterChat, | 14 | openCharacterChat, |
| 11 | selectCharacterById, | 15 | selectCharacterById, |
| 12 | sendSystemMessage, | 16 | sendSystemMessage, |
| 13 | system_message_types, | 17 | system_message_types, |
| 14 | } from '../script.js'; | 18 | } from '../script.js'; |
| 19 | import { is_group_generating } from './group-chats.js'; | ||
| 15 | import { t } from './i18n.js'; | 20 | import { t } from './i18n.js'; |
| 16 | import { renderTemplateAsync } from './templates.js'; | 21 | import { renderTemplateAsync } from './templates.js'; |
| 17 | import { timestampToMoment } from './utils.js'; | 22 | import { timestampToMoment } from './utils.js'; |
| 18 | 23 | ||
| 24 | const permanentAssistantAvatar = 'default_Assistant.png'; | ||
| 25 | |||
| 19 | export async function openWelcomeScreen() { | 26 | export async function openWelcomeScreen() { |
| 20 | const currentChatId = getCurrentChatId(); | 27 | const currentChatId = getCurrentChatId(); |
| 21 | if (currentChatId !== undefined) { | 28 | if (currentChatId !== undefined) { |
| @@ -28,7 +35,7 @@ export async function openWelcomeScreen() { | |||
| 28 | 35 | ||
| 29 | async function sendWelcomePanel() { | 36 | async function sendWelcomePanel() { |
| 30 | try { | 37 | try { |
| 31 | const chatElement = document.getElementById('chat'); | 38 | const chatElement = document.getElementById('chat'); |
| 32 | if (!chatElement) { | 39 | if (!chatElement) { |
| 33 | console.error('Chat element not found'); | 40 | console.error('Chat element not found'); |
| 34 | return; | 41 | return; |
| @@ -36,7 +43,7 @@ async function sendWelcomePanel() { | |||
| 36 | const chats = await getRecentChats(); | 43 | const chats = await getRecentChats(); |
| 37 | const templateData = { | 44 | const templateData = { |
| 38 | chats, | 45 | chats, |
| 39 | empty: !chats.length , | 46 | empty: !chats.length, |
| 40 | version: displayVersion, | 47 | version: displayVersion, |
| 41 | }; | 48 | }; |
| 42 | const template = await renderTemplateAsync('welcomePanel', templateData); | 49 | const template = await renderTemplateAsync('welcomePanel', templateData); |
| @@ -51,7 +58,7 @@ async function sendWelcomePanel() { | |||
| 51 | }); | 58 | }); |
| 52 | }); | 59 | }); |
| 53 | fragment.querySelector('button.openTemporaryChat').addEventListener('click', () => { | 60 | fragment.querySelector('button.openTemporaryChat').addEventListener('click', () => { |
| 54 | void newAssistantChat(); | 61 | void newAssistantChat({ temporary: true }); |
| 55 | }); | 62 | }); |
| 56 | chatElement.append(fragment.firstChild); | 63 | chatElement.append(fragment.firstChild); |
| 57 | } catch (error) { | 64 | } catch (error) { |
| @@ -114,7 +121,7 @@ async function getRecentChats() { | |||
| 114 | /** @type {RecentChat[]} */ | 121 | /** @type {RecentChat[]} */ |
| 115 | const data = await response.json(); | 122 | const data = await response.json(); |
| 116 | 123 | ||
| 117 | data.sort((a, b) => b.last_mes - a.last_mes).forEach((chat, index) => { | 124 | data.sort((a, b) => b.last_mes - a.last_mes).forEach((chat, index) => { |
| 118 | const character = characters.find(x => x.avatar === chat.avatar); | 125 | const character = characters.find(x => x.avatar === chat.avatar); |
| 119 | if (!character) { | 126 | if (!character) { |
| 120 | console.warn(`Character not found for chat: ${chat.file_name}`); | 127 | console.warn(`Character not found for chat: ${chat.file_name}`); |
| @@ -133,6 +140,72 @@ async function getRecentChats() { | |||
| 133 | return data; | 140 | return data; |
| 134 | } | 141 | } |
| 135 | 142 | ||
| 143 | export async function openPermanentAssistantChat({ tryCreate = true } = {}) { | ||
| 144 | const characterId = characters.findIndex(x => x.avatar === permanentAssistantAvatar); | ||
| 145 | if (characterId === -1) { | ||
| 146 | if (!tryCreate) { | ||
| 147 | console.error(`Character not found for avatar ID: ${permanentAssistantAvatar}. Cannot create.`); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | try { | ||
| 152 | console.log(`Character not found for avatar ID: ${permanentAssistantAvatar}. Creating new assistant.`); | ||
| 153 | await createPermanentAssistant(); | ||
| 154 | return openPermanentAssistantChat({ tryCreate: false }); | ||
| 155 | } | ||
| 156 | catch (error) { | ||
| 157 | console.error('Error creating permanent assistant:', error); | ||
| 158 | toastr.error(t`Failed to create ${neutralCharacterName}. See console for details.`); | ||
| 159 | return; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | try { | ||
| 164 | await selectCharacterById(characterId); | ||
| 165 | await doNewChat({ deleteCurrentChat: false }); | ||
| 166 | console.log(`Opened permanent assistant chat for ${neutralCharacterName}.`, getCurrentChatId()); | ||
| 167 | } catch (error) { | ||
| 168 | console.error('Error opening permanent assistant chat:', error); | ||
| 169 | toastr.error(t`Failed to open permanent assistant chat. See console for details.`); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | async function createPermanentAssistant() { | ||
| 174 | if (is_group_generating || is_send_press) { | ||
| 175 | throw new Error(t`Cannot create while generating.`); | ||
| 176 | } | ||
| 177 | |||
| 178 | const formData = new FormData(); | ||
| 179 | formData.append('ch_name', neutralCharacterName); | ||
| 180 | formData.append('file_name', permanentAssistantAvatar.replace('.png', '')); | ||
| 181 | |||
| 182 | const headers = getRequestHeaders(); | ||
| 183 | delete headers['Content-Type']; | ||
| 184 | |||
| 185 | const fetchResult = await fetch('/api/characters/create', { | ||
| 186 | method: 'POST', | ||
| 187 | headers: headers, | ||
| 188 | body: formData, | ||
| 189 | cache: 'no-cache', | ||
| 190 | }); | ||
| 191 | |||
| 192 | if (!fetchResult.ok) { | ||
| 193 | throw new Error(t`Creation request did not succeed.`); | ||
| 194 | } | ||
| 195 | |||
| 196 | await getCharacters(); | ||
| 197 | } | ||
| 198 | |||
| 199 | export async function openPermanentAssistantCard() { | ||
| 200 | const characterId = characters.findIndex(x => x.avatar === permanentAssistantAvatar); | ||
| 201 | if (characterId === -1) { | ||
| 202 | toastr.info(t`Assistant not found. Try sending a chat message.`); | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | |||
| 206 | await selectCharacterById(characterId); | ||
| 207 | } | ||
| 208 | |||
| 136 | export function initWelcomeScreen() { | 209 | export function initWelcomeScreen() { |
| 137 | const events = [event_types.CHAT_CHANGED, event_types.APP_READY]; | 210 | const events = [event_types.CHAT_CHANGED, event_types.APP_READY]; |
| 138 | for (const event of events) { | 211 | for (const event of events) { |
| @@ -6061,12 +6061,12 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 6061 | flex-wrap: nowrap; | 6061 | flex-wrap: nowrap; |
| 6062 | justify-content: space-between; | 6062 | justify-content: space-between; |
| 6063 | align-items: center; | 6063 | align-items: center; |
| 6064 | gap: 10px; | 6064 | gap: 5px; |
| 6065 | padding: 0 2px; | ||
| 6066 | } | 6065 | } |
| 6067 | 6066 | ||
| 6068 | .mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) { | 6067 | .mes_text div[data-type="assistant_note"]:has(.assistant_note_export) > div { |
| 6069 | flex: 1; | 6068 | flex: 1; |
| 6069 | text-align: left; | ||
| 6070 | } | 6070 | } |
| 6071 | 6071 | ||
| 6072 | .oneline-dropdown label { | 6072 | .oneline-dropdown label { |
| @@ -577,10 +577,10 @@ function charaFormatData(data, directories) { | |||
| 577 | _.set(char, 'mes_example', data.mes_example || ''); | 577 | _.set(char, 'mes_example', data.mes_example || ''); |
| 578 | 578 | ||
| 579 | // Old ST extension fields (for backward compatibility, will be deprecated) | 579 | // Old ST extension fields (for backward compatibility, will be deprecated) |
| 580 | _.set(char, 'creatorcomment', data.creator_notes); | 580 | _.set(char, 'creatorcomment', data.creator_notes || ''); |
| 581 | _.set(char, 'avatar', 'none'); | 581 | _.set(char, 'avatar', 'none'); |
| 582 | _.set(char, 'chat', data.ch_name + ' - ' + humanizedISO8601DateTime()); | 582 | _.set(char, 'chat', data.ch_name + ' - ' + humanizedISO8601DateTime()); |
| 583 | _.set(char, 'talkativeness', data.talkativeness); | 583 | _.set(char, 'talkativeness', data.talkativeness || 0.5); |
| 584 | _.set(char, 'fav', data.fav == 'true'); | 584 | _.set(char, 'fav', data.fav == 'true'); |
| 585 | _.set(char, 'tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []); | 585 | _.set(char, 'tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []); |
| 586 | 586 | ||
| @@ -604,7 +604,7 @@ function charaFormatData(data, directories) { | |||
| 604 | _.set(char, 'data.alternate_greetings', getAlternateGreetings(data)); | 604 | _.set(char, 'data.alternate_greetings', getAlternateGreetings(data)); |
| 605 | 605 | ||
| 606 | // ST extension fields to V2 object | 606 | // ST extension fields to V2 object |
| 607 | _.set(char, 'data.extensions.talkativeness', data.talkativeness); | 607 | _.set(char, 'data.extensions.talkativeness', data.talkativeness || 0.5); |
| 608 | _.set(char, 'data.extensions.fav', data.fav == 'true'); | 608 | _.set(char, 'data.extensions.fav', data.fav == 'true'); |
| 609 | _.set(char, 'data.extensions.world', data.world || ''); | 609 | _.set(char, 'data.extensions.world', data.world || ''); |
| 610 | 610 | ||
| @@ -1006,7 +1006,7 @@ router.post('/create', async function (request, response) { | |||
| 1006 | request.body.ch_name = sanitize(request.body.ch_name); | 1006 | request.body.ch_name = sanitize(request.body.ch_name); |
| 1007 | 1007 | ||
| 1008 | const char = JSON.stringify(charaFormatData(request.body, request.user.directories)); | 1008 | const char = JSON.stringify(charaFormatData(request.body, request.user.directories)); |
| 1009 | const internalName = getPngName(request.body.ch_name, request.user.directories); | 1009 | const internalName = request.body.file_name || getPngName(request.body.ch_name, request.user.directories); |
| 1010 | const avatarName = `${internalName}.png`; | 1010 | const avatarName = `${internalName}.png`; |
| 1011 | const chatsPath = path.join(request.user.directories.chats, internalName); | 1011 | const chatsPath = path.join(request.user.directories.chats, internalName); |
| 1012 | 1012 | ||