[wip] Welcome screen prototype

This commit is contained in:
Cohee
2025-05-12 00:28:42 +03:00
parent 420d568cd3
commit e975d37436
6 changed files with 493 additions and 52 deletions

View File

@@ -0,0 +1,71 @@
<div class="welcomePanel">
<div class="welcomeHeaderTitle">
<img src="img/logo.png" alt="SillyTavern Logo" class="welcomeHeaderLogo">
<span class="welcomeHeaderVersionDisplay">{{version}}</span>
</div>
<div class="welcomeHeader">
<div class="recentChatsTitle" data-i18n="Recent Chats">
Recent Chats
</div>
<div class="welcomeShortcuts">
<a class="menu_button menu_button_icon" target="_blank" href="https://docs.sillytavern.app/">
<i class="fa-solid fa-question-circle"></i>
<span data-i18n="Docs">Docs</span>
</a>
<a class="menu_button menu_button_icon" target="_blank" href="https://github.com/SillyTavern/SillyTavern">
<i class="fa-brands fa-github"></i>
<span data-i18n="GitHub">GitHub</span>
</a>
<a class="menu_button menu_button_icon" target="_blank" href="https://discord.gg/sillytavern">
<i class="fa-brands fa-discord"></i>
<span data-i18n="Discord">Discord</span>
</a>
<span class="welcomeShortcutsSeparator">&vert;</span>
<button class="openTemporaryChat menu_button menu_button_icon">
<i class="fa-solid fa-comment-dots"></i>
<span data-i18n="Temporary Chat">Temporary Chat</span>
</button>
</div>
</div>
<div class="welcomeRecent">
<div class="recentChatList">
{{#if empty}}
<div class="noRecentChat">
<i class="fa-solid fa-comment-dots"></i>
<span data-i18n="No recent chats">No recent chats</span>
</div>
{{/if}}
{{#each chats}}
{{#with this}}
<div class="recentChat" data-file="{{chat_name}}" data-avatar="{{avatar}}">
<div class="avatar" title="{{char_name}}">
<img src="{{char_thumbnail}}" alt="{{char_name}}">
</div>
<div class="recentChatInfo">
<div class="chatNameContainer">
<div class="chatName" title="{{file_name}}">
<strong class="characterName">{{char_name}}</strong>
<span>&ndash;</span>
<span>{{chat_name}}</span>
</div>
<small class="chatDate" title="{{date_full}}">{{date_short}}</small>
</div>
<div class="chatMessageContainer">
<div class="chatMessage" title="{{mes}}">
{{mes}}
</div>
<div class="chatStats">
<div class="counterBlock">
<i class="fa-solid fa-comment fa-xs"></i>
<small>{{chat_items}}</small>
</div>
<small class="fileSize">{{file_size}}</small>
</div>
</div>
</div>
</div>
{{/with}}
{{/each}}
</div>
</div>
</div>

View File

@@ -0,0 +1,135 @@
import {
characters,
displayVersion,
event_types,
eventSource,
getCurrentChatId,
getRequestHeaders,
getThumbnailUrl,
openCharacterChat,
selectCharacterById,
sendSystemMessage,
system_message_types,
} from '../script.js';
import { t } from './i18n.js';
import { renderTemplateAsync } from './templates.js';
import { timestampToMoment } from './utils.js';
export async function openWelcomeScreen() {
const currentChatId = getCurrentChatId();
if (currentChatId !== undefined) {
return;
}
await sendWelcomePanel();
sendSystemMessage(system_message_types.WELCOME_PROMPT);
}
async function sendWelcomePanel() {
try {
const chatElement = document.getElementById('chat');
if (!chatElement) {
console.error('Chat element not found');
return;
}
const chats = await getRecentChats();
const templateData = {
chats,
empty: !chats.length ,
version: displayVersion,
};
const template = await renderTemplateAsync('welcomePanel', templateData);
const fragment = document.createRange().createContextualFragment(template);
fragment.querySelectorAll('.recentChat').forEach((item) => {
item.addEventListener('click', () => {
const avatarId = item.getAttribute('data-avatar');
const fileName = item.getAttribute('data-file');
if (avatarId && fileName) {
void openRecentChat(avatarId, fileName);
}
});
});
fragment.querySelector('button.openTemporaryChat').addEventListener('click', () => {
toastr.info('This button does nothing at the moment. Try again later.');
});
chatElement.append(fragment.firstChild);
} catch (error) {
console.error('Welcome screen error:', error);
}
}
/**
* Opens a recent chat.
* @param {string} avatarId Avatar file name
* @param {string} fileName Chat file name
*/
async function openRecentChat(avatarId, fileName) {
const characterId = characters.findIndex(x => x.avatar === avatarId);
if (characterId === -1) {
console.error(`Character not found for avatar ID: ${avatarId}`);
return;
}
try {
await selectCharacterById(characterId);
await openCharacterChat(fileName);
} catch (error) {
console.error('Error opening recent chat:', error);
toastr.error(t`Failed to open recent chat. See console for details.`);
}
}
/**
* Gets the list of recent chats from the server.
* @returns {Promise<RecentChat[]>} List of recent chats
*
* @typedef {object} RecentChat
* @property {string} file_name Name of the chat file
* @property {string} chat_name Name of the chat (without extension)
* @property {string} file_size Size of the chat file
* @property {number} chat_items Number of items in the chat
* @property {string} mes Last message content
* @property {number} last_mes Timestamp of the last message
* @property {string} avatar Avatar URL
* @property {string} char_thumbnail Thumbnail URL
* @property {string} char_name Character name
* @property {string} date_short Date in short format
* @property {string} date_long Date in long format
*/
async function getRecentChats() {
const response = await fetch('/api/characters/recent', {
method: 'POST',
headers: getRequestHeaders(),
});
if (!response.ok) {
throw new Error('Failed to fetch recent chats');
}
/** @type {RecentChat[]} */
const data = await response.json();
data.sort((a, b) => b.last_mes - a.last_mes).forEach((chat, index) => {
const character = characters.find(x => x.avatar === chat.avatar);
if (!character) {
console.warn(`Character not found for chat: ${chat.file_name}`);
data.splice(index, 1);
return;
}
const chatTimestamp = timestampToMoment(chat.last_mes);
chat.char_name = character.name;
chat.date_short = chatTimestamp.format('l');
chat.date_long = chatTimestamp.format('LL LT');
chat.chat_name = chat.file_name.replace('.jsonl', '');
chat.char_thumbnail = getThumbnailUrl('avatar', character.avatar);
});
return data;
}
export function initWelcomeScreen() {
const events = [event_types.CHAT_CHANGED, event_types.APP_READY];
for (const event of events) {
eventSource.makeFirst(event, openWelcomeScreen);
}
}