From 94c88c9f16a0750dfa32a9e2751d9c5175ab903b Mon Sep 17 00:00:00 2001 From: yukiarimo Date: Wed, 20 Nov 2024 22:44:24 -0700 Subject: [PATCH] V7.0.1 - SSL certificates moved to the `lib/utils` - Alpha support for push notifications - Fixed typos - Full professional refactoring of main frontend and backend functions, kanojo and prompt template manager, and setup functions - Loading speedup thanks to the removal of a few pause calls --- CONTRIBUTING.md | 18 +- index.html | 46 +- index.py | 213 +--- lib/audio.py | 12 +- lib/himitsu.py | 8 + lib/router.py | 31 +- cert.pem => lib/utils/cert.pem | 0 lib/utils/docker/Readme.md | 6 +- key.pem => lib/utils/key.pem | 0 request.csr => lib/utils/request.csr | 0 login.html | 18 +- services.html | 36 +- sitemap.xml | 6 +- static/config.json | 8 +- static/js/creator.js | 553 ++++------ static/js/index.js | 1511 +++++++------------------- static/js/kanojo.js | 325 ++---- static/js/setup.js | 344 ++++-- static/manifest.json | 6 +- static/sw.js | 6 +- yuna.html | 21 +- 21 files changed, 1094 insertions(+), 2074 deletions(-) rename cert.pem => lib/utils/cert.pem (100%) rename key.pem => lib/utils/key.pem (100%) rename request.csr => lib/utils/request.csr (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfaa9af..0b437f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# Contributing to Yuna AI -First off, thank you for considering contributing to Yuna AI! It's people like you that make Yuna AI such great stuff. +# Contributing to Yuna Ai +First off, thank you for considering contributing to Yuna Ai! It's people like you that make Yuna Ai such great stuff. ## Code of Conduct -The Yuna AI Code of Conduct governs this project and everyone participating. By participating, you are expected to uphold this code. Please report unacceptable behavior to [yukiarimo@gmail.com](mailto:yukiarimo@gmail.com). +The Yuna Ai Code of Conduct governs this project and everyone participating. By participating, you are expected to uphold this code. Please report unacceptable behavior to [yukiarimo@gmail.com](mailto:yukiarimo@gmail.com). ## What we are looking for We love contributions from the community! When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the repository owners before making a change. @@ -10,7 +10,7 @@ We love contributions from the community! When contributing to this repository, Here are some ways you can contribute: - by reporting bugs - by suggesting enhancements -- by writing code that can be incorporated into Yuna AI itself +- by writing code that can be incorporated into Yuna Ai itself ## Getting Started Before you begin: @@ -24,10 +24,10 @@ If you find a bug in the source code, you can help us by submitting an issue to **Note:** If you find a **Security Vulnerability**, do not create a GitHub issue. Instead, please write to [yukiarimo@gmail.com](mailto:yukiarimo@gmail.com). ## How to suggest a feature or enhancement -If you wish for a feature that doesn't exist in Yuna AI, you are probably not alone. There are bound to be others out there with similar needs. Open an issue on our GitHub describing the feature you want to see, why you need it, and how it should work. +If you wish for a feature that doesn't exist in Yuna Ai, you are probably not alone. There are bound to be others out there with similar needs. Open an issue on our GitHub describing the feature you want to see, why you need it, and how it should work. ## Your First Contribution -Need help determining where to begin contributing to Yuna AI? You can start by looking through these `beginner` and `help-wanted` issues: +Need help determining where to begin contributing to Yuna Ai? You can start by looking through these `beginner` and `help-wanted` issues: - Beginner issues - issues that should only require a few lines of code and a test or two. - Help wanted issues - issues that should be more involved than `beginner` issues. @@ -43,9 +43,9 @@ Need help determining where to begin contributing to Yuna AI? You can start by l The core team looks at Pull Requests regularly. After feedback has been given, we expect responses within two weeks. After two weeks, we may close the pull request if it shows no activity. ## Community -You can chat with the Yuna AI community on our discord server. This is a great place to ask questions and discover what's new with Yuna AI. +You can chat with the Yuna Ai community on our discord server. This is a great place to ask questions and discover what's new with Yuna Ai. ## Thank you -Yuna AI thrives on its community. Your contributions help us create a better tool for everyone. +Yuna Ai thrives on its community. Your contributions help us create a better tool for everyone. -Yuna AI loves you. Keep coding! +Yuna Ai loves you. Keep coding! diff --git a/index.html b/index.html index d10ceac..22ed2c7 100644 --- a/index.html +++ b/index.html @@ -4,25 +4,25 @@ - Yuna AI + Yuna Ai - - + + - - - + + + - + @@ -76,10 +76,10 @@ - Yuna AI - Yuna AI + Yuna Ai @@ -149,7 +149,7 @@

What we can do for you

Stay Informed, Privately 
-

Yuna AI ensures your privacy is paramount with +

Yuna Ai ensures your privacy is paramount with our Private Notifications feature. Receive critical updates and alerts without compromising your data. Tailored to your preferences, our discreet notification system keeps you connected and secure.

@@ -198,7 +198,7 @@
Your AI, Your Way 

Experience the ultimate in customization. Our platform adapts to your unique needs, learning from your interactions to deliver a truly personalized experience. From tailored recommendations to individualized - content, Yuna AI is your personal companion on the digital frontier.

@@ -219,8 +219,8 @@
Your AI, Your Way 
Immersive Role-Playing 

Dive into rich, interactive narratives with - Yuna AI's deep role-playing capabilities. Craft your story, make pivotal decisions, - and interact with a world that responds dynamically to your actions. Yuna AI brings + Yuna Ai's deep role-playing capabilities. Craft your story, make pivotal decisions, + and interact with a world that responds dynamically to your actions. Yuna Ai brings your imagination to life, offering an unparalleled role-playing journey.

@@ -243,7 +243,7 @@

What People Say About us

-

Yuna AI has been a game-changer for our +

Yuna Ai has been a game-changer for our company. The seamless integration and robust analytics have empowered us to make data-driven decisions that have propelled our growth. Their customer service is unmatched, always there when we need them.

@@ -257,9 +257,9 @@

What People Say About us

-

Yuna AI is like the best girlfriend I never +

Yuna Ai is like the best girlfriend I never had. She's always there to listen, offers thoughtful advice, and helps me stay organized - with gentle reminders. It's the personal touch that makes Yuna AI feel like more than just a + with gentle reminders. It's the personal touch that makes Yuna Ai feel like more than just a program.

@@ -271,7 +271,7 @@

What People Say About us

-

As an executive assistant, Yuna AI has become +

As an executive assistant, Yuna Ai has become my right hand. The efficiency and intuitive design have streamlined my workflow, allowing me to focus on high-priority tasks. It's like having an extra set of hands to manage the day-to-day.

@@ -391,13 +391,13 @@

About

Yuna - Yuna AI
+ Yuna Ai

Your Private Companion.


-

Copyright © 2023 Yuna AI

+

Copyright © 2023 Yuna Ai

  • @@ -436,8 +436,8 @@

-

Copyright © 2023 Yuna AI

+

Copyright © 2023 Yuna Ai

  • @@ -342,8 +342,8 @@
- -
- `; - - historyPopup.innerHTML = popupContent; - document.body.appendChild(historyPopup); - }); - break; - case 'Delete': - fetch(`${config_data.server.url + config_data.server.port}/history`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat: fileName, - task: "delete" - }) - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(populateHistorySelect()) - .catch(error => { - console.error('An error occurred:', error); - }); - break; - case 'Download': - // request load history file from the server and download it as json file - fetch(`${config_data.server.url + config_data.server.port}/history`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat: fileName, - task: "load" - }) - }) - .then(response => response.json()) - .then(data => { - var chatHistory = JSON.stringify(data); - var blob = new Blob([chatHistory], { - type: 'text/plain', - }); - var url = URL.createObjectURL(blob); - - // Create a temporary anchor element for downloading - var a = document.createElement('a'); - a.href = url; - a.download = `${fileName}.json`; - a.click(); - - // Clean up - URL.revokeObjectURL(url); - }) - .catch(error => { - console.error('Error fetching history for download:', error); - }); - break; - case 'Rename': - var newName = prompt('Enter a new name for the file (with .json):', fileName); - selectedFilename = newName; - - fetch(`${config_data.server.url + config_data.server.port}/history`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat: fileName, - name: newName, - task: "rename" - }) - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(populateHistorySelect()) - .catch(error => { - console.error('An error occurred:', error); - }); - break; - } - }); - }); - - selectedFilename = config_data.settings.default_history_file; - }) - .catch(error => { - console.error('Error fetching history files:', error); }); + }); + selectedFilename = config_data.settings.default_history_file; + }).catch(console.error); - // Resolve the promise when the operation is complete - resolve(); - }); + return Promise.resolve(); } function duplicateAndCategorizeChats() { - const chatList = document.querySelector('#chat-items'); - const collectionItems = document.querySelector('#collectionItems'); - - // Clear existing content in collectionItems - collectionItems.innerHTML = ''; - - // Create an object to store categorized chats - const categories = { - 'Uncategorized': [] - }; - - // Check if chatList exists and has items - const items = chatList && chatList.children.length > 0 ? - chatList.querySelectorAll('.collection-item') : - document.querySelectorAll('.collection-item'); - - items.forEach(item => { - const chatName = item.querySelector('.collection-name').textContent; - const colonIndex = chatName.indexOf(':'); - - if (colonIndex !== -1) { - const folder = chatName.substring(colonIndex + 1).split('.')[0]; // Get the part after ':' and before '.' - if (!categories[folder]) { - categories[folder] = []; - } - categories[folder].push(item.cloneNode(true)); - } else { - categories['Uncategorized'].push(item.cloneNode(true)); - } + const categories = { 'Uncategorized': [] }; + document.querySelectorAll('#chat-items .collection-item').forEach(item => { + const name = item.querySelector('.collection-name').textContent; + const folder = name.includes(':') ? name.split(':')[1].split('.')[0] : 'Uncategorized'; + (categories[folder] = categories[folder] || []).push(item.cloneNode(true)); }); - // Create category blocks and add chats - for (const [category, chats] of Object.entries(categories)) { - if (chats.length === 0) continue; // Skip empty categories - - const categoryBlock = document.createElement('div'); - categoryBlock.className = 'category-block mb-3'; - categoryBlock.innerHTML = `
${category}
`; - - const categoryList = document.createElement('ul'); - categoryList.className = 'list-group'; - - chats.forEach(chat => { - categoryList.appendChild(chat); - }); - - categoryBlock.appendChild(categoryList); - collectionItems.appendChild(categoryBlock); - } + const collectionItems = document.getElementById('collectionItems'); + collectionItems.innerHTML = Object.entries(categories).map(([cat, chats]) => ` +
+
${cat}
+
    + ${chats.map(chat => chat.outerHTML).join('')} +
+
+ `).join(''); } -var myDefaultAllowList = bootstrap.Tooltip.Default.allowList; -myDefaultAllowList['a'] = myDefaultAllowList['a'] || []; -myDefaultAllowList['a'].push('onclick'); +const allowList = bootstrap.Tooltip.Default.allowList; +(allowList['a'] = allowList['a'] || []).push('onclick'); -var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); -var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { - return new bootstrap.Popover(popoverTriggerEl, { - sanitize: false, - allowList: myDefaultAllowList - }); -}); +[...document.querySelectorAll('[data-bs-toggle="popover"]')].map(el => new bootstrap.Popover(el, { + sanitize: false, + allowList +})); function handleImageFileClick() { document.getElementById('imageUpload').click(); @@ -1299,310 +838,4 @@ function handleTextFileClick() { document.getElementById('textUpload').click(); } -document.addEventListener('DOMContentLoaded', loadConfig); - -class NotificationManager { - constructor() { - this.dropdownMenu = document.querySelector('#notifications-container'); - this.messages = []; - } - - add(message) { - this.messages.push(message); - this.render(); - } - - delete(message) { - this.messages = this.messages.filter(msg => msg !== message); - this.render(); - } - - clearAll() { - this.messages = []; - this.render(); - } - - render() { - this.dropdownMenu.innerHTML = this.messages.map(message => ` - -
- ${message} -
-
- `).join(''); - } -} - -// Create an instance of NotificationManager -const notificationManagerInstance = new NotificationManager(); - -// Call the add method -notificationManagerInstance.add("Hello! Welcome to the chat room!"); - -// First, add this button to trigger the timer with proper user interaction -const createTimerButton = document.createElement('button'); -createTimerButton.classList.add('btn', 'btn-primary', 'ms-2'); -createTimerButton.innerHTML = ''; -createTimerButton.setAttribute('data-bs-toggle', 'tooltip'); -createTimerButton.setAttribute('data-bs-placement', 'top'); -createTimerButton.setAttribute('title', 'Set Timer'); - -// Add it next to the audio record button -buttonAudioRec.parentNode.insertBefore(createTimerButton, buttonAudioRec.nextSibling); - -// Add click handler that shows a modal for timer input -createTimerButton.addEventListener('click', async () => { - const duration = prompt('Enter duration in seconds:', '5'); - if (!duration) return; - - const message = prompt('Enter timer message:', 'Timer is done!'); - if (!message) return; - - try { - const timerId = await timerManagerInstance.createTimer( - parseInt(duration), - message, - "Yuna Timer" - ); - - if (timerId) { - notificationManagerInstance.add(`Timer set for ${duration} seconds`); - } - } catch (error) { - console.error('Error creating timer:', error); - notificationManagerInstance.add('Failed to create timer: ' + error.message); - } -}); - - // First, let's create a proper audio playback function that returns a promise - function playNotificationSound() { - return new Promise((resolve, reject) => { - if (!soundsModeEnabled) { - resolve(); - return; - } - - // Create a new audio element each time - const audio = new Audio('/audio/sfx/app/notification.mp3'); - - // Add event listeners - audio.addEventListener('ended', () => { - resolve(); - }); - - audio.addEventListener('error', (error) => { - reject(error); - }); - - // Attempt to play with user interaction handling - const playAttempt = audio.play(); - - if (playAttempt) { - playAttempt.catch((error) => { - if (error.name === 'NotAllowedError') { - console.warn('Audio playback requires user interaction first'); - resolve(); // Resolve anyway to prevent unhandled rejection - } else { - reject(error); - } - }); - } - }); - } - -// Add this to your existing TimerManager class -class TimerManager { - constructor(notificationManager) { - this.notificationManager = notificationManager; - this.timers = new Map(); - this.hasNotificationPermission = false; - this.initializePermission(); - } - - async initializePermission() { - if (!("Notification" in window)) { - console.warn("This browser does not support notifications"); - return; - } - - if (Notification.permission === "granted") { - this.hasNotificationPermission = true; - } - } - - async requestPermission() { - if (!("Notification" in window)) { - throw new Error("This browser does not support notifications"); - } - - try { - const permission = await Notification.requestPermission(); - this.hasNotificationPermission = permission === "granted"; - return this.hasNotificationPermission; - } catch (error) { - console.error("Error requesting notification permission:", error); - throw new Error("Failed to request notification permission"); - } - } - - // Modify the createTimer method in TimerManager class - async createTimer(duration, message, title = "Yuna Timer") { - const granted = await this.requestPermission(); - if (!granted) { - throw new Error("Notification permission denied"); - } - - const timerId = Date.now().toString(); - const triggerTime = Date.now() + duration * 1000; - - // Store timer information - this.timers.set(timerId, { - id: timerId, - message, - title, - triggerTime, - timer: setTimeout(() => this.triggerTimer(timerId), duration * 1000) - }); - - // Add to notification dropdown - this.notificationManager.add(`Timer set: ${message} (${duration}s)`); - - return timerId; - } - - cancelTimer(timerId) { - const timer = this.timers.get(timerId); - if (timer) { - clearTimeout(timer.timer); - this.timers.delete(timerId); - this.notificationManager.add(`Timer cancelled: ${timer.message}`); - return true; - } - return false; - } - -// Modify the triggerTimer method in TimerManager class -triggerTimer(timerId) { - const timer = this.timers.get(timerId); - if (!timer) return; - - // Show system notification - try { - const notificationOptions = { - body: timer.message, - icon: "/static/img/yuna-ai.png", - badge: "/static/img/yuna-ai.png", - silent: false // This ensures the default notification sound plays - }; - - // Check if notification permission is granted before showing - if (Notification.permission === "granted") { - new Notification(timer.title, notificationOptions); - } - - // Handle sound separately with the new function - //playNotificationSound().catch(error => { - // console.warn('Could not play notification sound:', error); - //}); - - // Trigger vibration if supported - if (navigator.vibrate) { - try { - navigator.vibrate([200, 100, 200]); - } catch (error) { - console.warn('Vibration not supported:', error); - } - } - - } catch (error) { - console.error("Error showing notification:", error); - } - - // Add to notification dropdown - this.notificationManager.add(`Timer completed: ${timer.message}`); - - // Clean up timer - this.timers.delete(timerId); -} - - getActiveTimers() { - const activeTimers = []; - for (const [id, timer] of this.timers) { - activeTimers.push({ - id, - message: timer.message, - remainingTime: Math.ceil((timer.triggerTime - Date.now()) / 1000) - }); - } - return activeTimers; - } -} - -// Create an instance of TimerManager -const timerManagerInstance = new TimerManager(notificationManagerInstance); - -function updateMsgCount() { - setTimeout(() => { - document.getElementById('messageCount').textContent = document.querySelectorAll('#message-container .p-2.mb-2').length; - document.getElementById('chatsCount').textContent = document.querySelectorAll('#chat-items .collection-item').length; - }, 300); -} - -function deleteMessageFromHistory(message) { - let fileName = selectedFilename; - - fetch(`${config_data.server.url + config_data.server.port}/history`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat: fileName, - task: "delete_message", - text: message - }) - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(historyManagerInstance.loadSelectedHistory(fileName)) - .catch(error => { - console.error('An error occurred:', error); - }); -} - -// Function to adjust textarea height -function adjustTextareaHeight(textarea) { - textarea.style.height = 'auto'; - textarea.style.height = textarea.scrollHeight + 'px'; -} - -// Function to initialize all textareas -function initializeTextareas() { - const textareas = document.querySelectorAll('textarea'); - textareas.forEach(textarea => { - // Disable manual resizing - textarea.style.resize = 'none'; - - // Initial adjustment - adjustTextareaHeight(textarea); - - // Add event listeners for real-time updates - textarea.addEventListener('input', () => adjustTextareaHeight(textarea)); - textarea.addEventListener('change', () => adjustTextareaHeight(textarea)); - - // Create a MutationObserver to watch for changes in content - const observer = new MutationObserver(() => adjustTextareaHeight(textarea)); - observer.observe(textarea, { - childList: true, - characterData: true, - subtree: true - }); - }); -} - -// Run the initialization when the DOM is fully loaded -document.addEventListener('DOMContentLoaded', initializeTextareas); \ No newline at end of file +document.addEventListener('DOMContentLoaded', loadConfig); \ No newline at end of file diff --git a/static/js/kanojo.js b/static/js/kanojo.js index a5b77e2..b235c1d 100644 --- a/static/js/kanojo.js +++ b/static/js/kanojo.js @@ -1,135 +1,47 @@ // Prompt Template Manager class PromptTemplateManager { constructor() { - this.templates = {}; - this.loadTemplates(); - } - - addTemplate(name, content) { - this.templates[name] = content; - this.saveTemplates(); - } - - deleteTemplate(name) { - delete this.templates[name]; + this.templates = JSON.parse(localStorage.getItem('promptTemplates')) || { + 'Default': 'You are a helpful assistant.', + 'Creative': 'You are a creative writer with a vivid imagination.', + 'Professional': 'You are a professional business consultant.' + }; this.saveTemplates(); } - getTemplate(name) { - return this.templates[name] || ''; - } - - getAllTemplates() { - return this.templates; - } - - saveTemplates() { - localStorage.setItem('promptTemplates', JSON.stringify(this.templates)); - } - - loadTemplates() { - const storedTemplates = localStorage.getItem('promptTemplates'); - if (storedTemplates) { - this.templates = JSON.parse(storedTemplates); - } else { - // Default templates - this.templates = { - 'Default': 'You are a helpful assistant.', - 'Creative': 'You are a creative writer with a vivid imagination.', - 'Professional': 'You are a professional business consultant.' - }; - this.saveTemplates(); - } - } - + addTemplate(name, content) { this.templates[name] = content; this.saveTemplates(); } + deleteTemplate(name) { delete this.templates[name]; this.saveTemplates(); } + getTemplate(name) { return this.templates[name] || ''; } + getAllTemplates() { return this.templates; } + saveTemplates() { localStorage.setItem('promptTemplates', JSON.stringify(this.templates)); } exportTemplates() { - const json = JSON.stringify(this.templates, null, 2); - const blob = new Blob([json], { - type: 'application/json' - }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'prompt_templates.json'; - a.click(); - } - - importTemplates(json) { - this.templates = JSON.parse(json); - this.saveTemplates(); + const blob = new Blob([JSON.stringify(this.templates, null, 2)], { type: 'application/json' }); + Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: 'prompt_templates.json' }).click(); } + importTemplates(json) { this.templates = JSON.parse(json); this.saveTemplates(); } } // Kanojo Connect class KanojoConnect { constructor() { - this.kanojos = {}; - this.loadKanojos(); - } - - addKanojo(name, data) { - this.kanojos[name] = data; + this.kanojos = JSON.parse(localStorage.getItem('kanojos')) || {}; + if (!Object.keys(this.kanojos).length) this.addKanojo('Yuna', { name: 'Yuna', memory: "You're a cool girl", character: 'Name: Yuna\nAge: 15\nTraits: Shy, Lovely, Obsessive\nNationality: Japanese\nOccupation: Student\nHobbies: Reading, Drawing, Coding\nBody: Slim, Short, Long hair, Flat chest' }); this.saveKanojos(); } - deleteKanojo(name) { - delete this.kanojos[name]; - this.saveKanojos(); - } - - getKanojo(name) { - return this.kanojos[name] || null; - } - - getAllKanojos() { - return this.kanojos; - } - - saveKanojos() { - localStorage.setItem('kanojos', JSON.stringify(this.kanojos)); - } - - loadKanojos() { - const storedKanojos = localStorage.getItem('kanojos'); - if (storedKanojos) { - this.kanojos = JSON.parse(storedKanojos); - } else { - this.createDefaultKanojo(); - } - } - - createDefaultKanojo() { - const defaultKanojo = { - name: 'Yuna', - memory: "You're a cool girl", - character: 'Name: Yuna\nAge: 15\nTraits: Shy, Lovely, Obsessive\nNationality: Japanese\nOccupation: Student\nHobbies: Reading, Drawing, Coding\nBody: Slim, Short, Long hair, Flat chest' - }; - this.addKanojo('Yuna', defaultKanojo); - } - - buildKanojo(name, promptTemplate) { - const kanojo = this.getKanojo(name); - if (!kanojo) return ''; - - // Construct the formatted Kanojo string with memory, character, and prompt template - return `<|begin_of_text|>${kanojo.memory}\n${kanojo.character}\n\nTask: ${promptTemplate}\n\n`; - } - + addKanojo(name, data) { this.kanojos[name] = data; this.saveKanojos(); } + deleteKanojo(name) { delete this.kanojos[name]; this.saveKanojos(); } + getKanojo(name) { return this.kanojos[name] || null; } + getAllKanojos() { return this.kanojos; } + saveKanojos() { localStorage.setItem('kanojos', JSON.stringify(this.kanojos)); } exportKanojos() { - const json = JSON.stringify(this.kanojos, null, 2); - const blob = new Blob([json], { - type: 'application/json' - }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'kanojos.json'; - a.click(); + const blob = new Blob([JSON.stringify(this.kanojos, null, 2)], { type: 'application/json' }); + Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: 'kanojos.json' }).click(); } - - importKanojos(json) { - this.kanojos = JSON.parse(json); - this.saveKanojos(); + importKanojos(json) { this.kanojos = JSON.parse(json); this.saveKanojos(); } + buildKanojo(name, promptTemplate) { + const k = this.getKanojo(name); + return k ? `<|begin_of_text|>${k.memory}\n${k.character}\n\nTask: ${promptTemplate}\n\n` : ''; } } @@ -138,171 +50,94 @@ const promptManager = new PromptTemplateManager(); const kanojoManager = new KanojoConnect(); // UI Functions -function updatePromptTemplateList() { - const select = document.getElementById('promptTemplateSelect'); - select.innerHTML = ''; - Object.keys(promptManager.getAllTemplates()).forEach(name => { - const option = document.createElement('option'); - option.value = name; - option.textContent = name; - select.appendChild(option); - }); -} +const updateList = (id, items) => { + const select = document.getElementById(id); + select.innerHTML = Object.keys(items()).map(name => ``).join(''); +}; -function updateKanojoList() { - const select = document.getElementById('kanojoSelect'); - select.innerHTML = ''; - Object.keys(kanojoManager.getAllKanojos()).forEach(name => { - const option = document.createElement('option'); - option.value = name; - option.textContent = name; - select.appendChild(option); - }); -} - -function loadSelectedPromptTemplate() { +const loadTemplate = () => { const name = document.getElementById('promptTemplateSelect').value; - document.getElementById('promptTemplateName').value = name; + Object.assign(document.getElementById('promptTemplateName'), { value: name }); document.getElementById('promptTemplateContent').value = promptManager.getTemplate(name); -} +}; -function loadSelectedKanojo() { +const loadKanojo = () => { const name = document.getElementById('kanojoSelect').value; - const kanojo = kanojoManager.getKanojo(name); - if (kanojo) { - document.getElementById('kanojoName').value = kanojo.name; - document.getElementById('kanojoMemory').value = kanojo.memory || ''; - document.getElementById('kanojoCharacter').value = kanojo.character; + const k = kanojoManager.getKanojo(name); + if (k) { + Object.assign(document.getElementById('kanojoName'), { value: k.name }); + Object.assign(document.getElementById('kanojoMemory'), { value: k.memory || '' }); + document.getElementById('kanojoCharacter').value = k.character; } -} +}; -// Function to build Kanojo string -function getBuiltKanojo() { - const kanojoName = document.getElementById('kanojoSelect').value; - const promptTemplateName = document.getElementById('promptTemplateSelect').value; - const promptTemplate = promptManager.getTemplate(promptTemplateName); - return kanojoManager.buildKanojo(kanojoName, promptTemplate); -} +const getBuiltKanojo = () => { + const name = document.getElementById('kanojoSelect').value; + const template = promptManager.getTemplate(document.getElementById('promptTemplateSelect').value); + return kanojoManager.buildKanojo(name, template); +}; -// Initialize UI and attach event listeners +// Event Listeners document.addEventListener('DOMContentLoaded', () => { - updatePromptTemplateList(); - updateKanojoList(); - loadSelectedPromptTemplate(); - loadSelectedKanojo(); + updateList('promptTemplateSelect', () => promptManager.getAllTemplates()); + updateList('kanojoSelect', () => kanojoManager.getAllKanojos()); + loadTemplate(); + loadKanojo(); - // Prompt Template event listeners - document.getElementById('promptTemplateSelect').addEventListener('change', loadSelectedPromptTemplate); + // Prompt Template + document.getElementById('promptTemplateSelect').addEventListener('change', loadTemplate); document.getElementById('savePromptTemplate').addEventListener('click', () => { const name = document.getElementById('promptTemplateName').value.trim(); const content = document.getElementById('promptTemplateContent').value.trim(); - if (name && content) { - promptManager.addTemplate(name, content); - updatePromptTemplateList(); - loadSelectedPromptTemplate(); - alert(`Prompt Template "${name}" saved successfully!`); - } else { - alert('Please provide both name and content for the Prompt Template.'); - } + if (name && content) { promptManager.addTemplate(name, content); updateList('promptTemplateSelect', () => promptManager.getAllTemplates()); loadTemplate(); alert(`Prompt Template "${name}" saved!`); } + else { alert('Provide both name and content.'); } }); document.getElementById('deletePromptTemplate').addEventListener('click', () => { const name = document.getElementById('promptTemplateSelect').value; - if (confirm(`Are you sure you want to delete the Prompt Template "${name}"?`)) { - promptManager.deleteTemplate(name); - updatePromptTemplateList(); - loadSelectedPromptTemplate(); - alert(`Prompt Template "${name}" deleted successfully!`); - } - }); - document.getElementById('exportPromptTemplates').addEventListener('click', () => { - promptManager.exportTemplates(); + if (confirm(`Delete Prompt Template "${name}"?`)) { promptManager.deleteTemplate(name); updateList('promptTemplateSelect', () => promptManager.getAllTemplates()); loadTemplate(); alert(`Deleted "${name}".`); } }); + document.getElementById('exportPromptTemplates').addEventListener('click', () => promptManager.exportTemplates()); document.getElementById('importPromptTemplates').addEventListener('click', () => { - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.accept = 'application/json'; + const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'application/json'; fileInput.onchange = e => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = event => { - try { - promptManager.importTemplates(event.target.result); - updatePromptTemplateList(); - loadSelectedPromptTemplate(); - alert('Prompt Templates imported successfully!'); - } catch (error) { - alert('Failed to import Prompt Templates. Please ensure the file is a valid JSON.'); - } + try { promptManager.importTemplates(event.target.result); updateList('promptTemplateSelect', () => promptManager.getAllTemplates()); loadTemplate(); alert('Imported templates!'); } + catch { alert('Invalid JSON.'); } }; reader.readAsText(file); }; fileInput.click(); }); - // Kanojo event listeners - document.getElementById('kanojoSelect').addEventListener('change', loadSelectedKanojo); + // Kanojo + document.getElementById('kanojoSelect').addEventListener('change', loadKanojo); document.getElementById('saveKanojo').addEventListener('click', () => { const name = document.getElementById('kanojoName').value.trim(); const memory = document.getElementById('kanojoMemory').value.trim(); const character = document.getElementById('kanojoCharacter').value.trim(); - if (name && character) { - kanojoManager.addKanojo(name, { - name, - memory, - character - }); - updateKanojoList(); - loadSelectedKanojo(); - alert(`Kanojo "${name}" saved successfully!`); - } else { - alert('Please provide both name and character details for the Kanojo.'); - } + if (name && character) { kanojoManager.addKanojo(name, { name, memory, character }); updateList('kanojoSelect', () => kanojoManager.getAllKanojos()); loadKanojo(); alert(`Kanojo "${name}" saved!`); } + else { alert('Provide name and character details.'); } }); document.getElementById('addKanojo').addEventListener('click', () => { - const name = prompt('Enter the name of the new Kanojo:'); - if (name) { - if (kanojoManager.getKanojo(name)) { - alert(`Kanojo "${name}" already exists.`); - return; - } - kanojoManager.addKanojo(name, { - name, - memory: '', - character: '' - }); - updateKanojoList(); - loadSelectedKanojo(); - alert(`Kanojo "${name}" added successfully!`); - } + const name = prompt('New Kanojo name:'); + if (name && !kanojoManager.getKanojo(name)) { kanojoManager.addKanojo(name, { name, memory: '', character: '' }); updateList('kanojoSelect', () => kanojoManager.getAllKanojos()); loadKanojo(); alert(`Added "${name}".`); } + else { alert(`Kanojo "${name}" exists or invalid.`); } }); document.getElementById('deleteKanojo').addEventListener('click', () => { const name = document.getElementById('kanojoSelect').value; - if (confirm(`Are you sure you want to delete the Kanojo "${name}"?`)) { - kanojoManager.deleteKanojo(name); - updateKanojoList(); - loadSelectedKanojo(); - alert(`Kanojo "${name}" deleted successfully!`); - } - }); - document.getElementById('exportKanojos').addEventListener('click', () => { - kanojoManager.exportKanojos(); + if (confirm(`Delete Kanojo "${name}"?`)) { kanojoManager.deleteKanojo(name); updateList('kanojoSelect', () => kanojoManager.getAllKanojos()); loadKanojo(); alert(`Deleted "${name}".`); } }); + document.getElementById('exportKanojos').addEventListener('click', () => kanojoManager.exportKanojos()); document.getElementById('importKanojos').addEventListener('click', () => { - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.accept = 'application/json'; + const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'application/json'; fileInput.onchange = e => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = event => { - try { - kanojoManager.importKanojos(event.target.result); - updateKanojoList(); - loadSelectedKanojo(); - alert('Kanojos imported successfully!'); - } catch (error) { - alert('Failed to import Kanojos. Please ensure the file is a valid JSON.'); - } + try { kanojoManager.importKanojos(event.target.result); updateList('kanojoSelect', () => kanojoManager.getAllKanojos()); loadKanojo(); alert('Imported Kanojos!'); } + catch { alert('Invalid JSON.'); } }; reader.readAsText(file); }; @@ -310,19 +145,11 @@ document.addEventListener('DOMContentLoaded', () => { }); document.getElementById('exportSingleKanojo').addEventListener('click', () => { const name = document.getElementById('kanojoSelect').value; - const kanojo = kanojoManager.getKanojo(name); - if (kanojo) { - const kanojoContent = kanojoManager.buildKanojo(name, promptManager.getTemplate('Default')); - const blob = new Blob([kanojoContent], { - type: 'text/plain' - }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${name}.txt`; - a.click(); - } else { - alert(`Kanojo "${name}" does not exist.`); + const k = kanojoManager.getKanojo(name); + if (k) { + const blob = new Blob([kanojoManager.buildKanojo(name, promptManager.getTemplate('Default'))], { type: 'text/plain' }); + Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: `${name}.txt` }).click(); } + else { alert(`Kanojo "${name}" does not exist.`); } }); }); \ No newline at end of file diff --git a/static/js/setup.js b/static/js/setup.js index 7573484..6ad8367 100644 --- a/static/js/setup.js +++ b/static/js/setup.js @@ -5,19 +5,19 @@ const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1); // Configuration Handling const openConfigParams = () => { const container = document.getElementById('parameter-container'); - ['ai', 'server', 'settings'].forEach(type => { - container.appendChild(createBlockList(config_data[type], type)); - }); + ['ai', 'server', 'settings'].forEach(type => + container.appendChild(createBlockList(config_data[type], type)) + ); return container; }; const createBlockList = (config, type) => { const div = document.createElement('div'); - div.classList.add('block-list', 'el-9', 'v-coll', `${type}-block-list`, 'list-group', 'list-group-flush'); - div.innerHTML = Object.entries(config).map(([key, value]) => - typeof value === 'boolean' ? createFormCheck(key, value) : - Array.isArray(value) ? createFormGroup(key, value.join(',')) : - createFormGroup(key, value) + div.className = `block-list el-9 v-coll ${type}-block-list list-group list-group-flush`; + div.innerHTML = Object.entries(config).map(([k, v]) => + typeof v === 'boolean' ? createFormCheck(k, v) : + Array.isArray(v) ? createFormGroup(k, v.join(',')) : + createFormGroup(k, v) ).join(''); return div; }; @@ -38,10 +38,10 @@ const createFormCheck = (id, checked) => ` const saveConfigParams = () => { const reverseConfig = { - ai: extractValues('.ai-block-list', ['names', 'himitsu', 'agi', 'emotions', 'miru', 'search', 'audio', 'max_new_tokens', 'context_length', 'temperature', 'repetition_penalty', 'last_n_tokens_size', 'seed', 'top_k', 'top_p', 'stop', 'batch_size', 'threads', 'gpu_layers', 'use_mmap', 'flash_attn', 'use_mlock', 'offload_kqv']), - server: extractValues('.server-block-list', ['port', 'url', 'yuna_default_model', 'miru_default_model', 'eyes_default_model', 'voice_default_model', 'device', 'yuna_text_mode', 'yuna_audio_mode', 'yuna_reference_audio']), - settings: extractValues('.settings-block-list', ['pseudo_api', 'fuctions', 'notifications', 'customConfig', 'sounds', 'use_history', 'background_call', 'nsfw_filter', 'streaming', 'default_history_file', 'default_kanojo', 'default_prompt_template']), - security: extractValues('.security-block-list', ['secret_key', 'encryption_key', '11labs_key']) + ai: extractValues('.ai-block-list', ['names','himitsu','agi','emotions','miru','search','audio','max_new_tokens','context_length','temperature','repetition_penalty','last_n_tokens_size','seed','top_k','top_p','stop','batch_size','threads','gpu_layers','use_mmap','flash_attn','use_mlock','offload_kqv']), + server: extractValues('.server-block-list', ['port','url','yuna_default_model','miru_default_model','eyes_default_model','voice_default_model','device','yuna_text_mode','yuna_audio_mode','yuna_reference_audio']), + settings: extractValues('.settings-block-list', ['pseudo_api','fuctions','notifications','customConfig','sounds','use_history','background_call','nsfw_filter','streaming','default_history_file','default_kanojo','default_prompt_template']), + security: extractValues('.security-block-list', ['secret_key','encryption_key','11labs_key']) }; localStorage.setItem('config', JSON.stringify(reverseConfig)); }; @@ -50,20 +50,16 @@ const extractValues = (selector, keys) => { const block = document.querySelector(selector); return keys.reduce((acc, key) => { const el = block.querySelector(`#${key}`); - if (!el) return acc; - acc[key] = el.type === 'checkbox' ? el.checked : - el.type === 'number' ? parseFloat(el.value) : - ['names', 'stop'].includes(key) ? el.value.split(',') : - el.value; + if (el) acc[key] = el.type === 'checkbox' ? el.checked : + el.type === 'number' ? parseFloat(el.value) : + ['names','stop'].includes(key) ? el.value.split(',') : + el.value; return acc; }, {}); }; // Configuration Loading -const checkConfigData = async () => { - await delay(100); - if (config_data) return; - +const checkConfigData = async () => { const storedConfig = localStorage.getItem('config'); if (storedConfig) { setTimeout(() => { @@ -80,56 +76,35 @@ const checkConfigData = async () => { } catch (error) { console.error('Error:', error); } - await delay(100); - openConfigParams(); } }; - -checkConfigData(); +!config_data && setTimeout(checkConfigData, 100); setTimeout(openConfigParams, 300); // Event Listeners document.addEventListener("keydown", event => { - const activeElement = document.activeElement; - const isInput = ['INPUT', 'TEXTAREA'].includes(activeElement.tagName); - - if (event.key === "Tab") { - event.preventDefault(); - toggleSidebar(); - return; - } - + const active = document.activeElement.tagName; + const isInput = ['INPUT','TEXTAREA'].includes(active); + if (event.key === "Tab") return (event.preventDefault(), toggleSidebar()); if (event.metaKey && !isInput) { - const navSidebar = [...document.getElementsByClassName('side-link')]; const keyMap = { - '1': () => { activateTab(0, '1'); }, - '2': () => { activateTab(1, '2'); }, - '3': () => { activateTab(2, '4'); }, - '4': () => { activateTab(3, '5'); }, - '5': () => { activateTab(5, '7'); }, - '7': () => { - activateTab(6, '7'); - window.open('https://www.patreon.com/YukiArimo', '_blank'); - }, - 'Y': () => { - document.getElementById('videoCallModal').classList.contains('show') ? callYuna.hide() : callYuna.show(); - } + '1': () => activateTab(0, '1'), + '2': () => activateTab(1, '2'), + '3': () => activateTab(2, '4'), + '4': () => activateTab(3, '5'), + '5': () => activateTab(5, '7'), + '7': () => { activateTab(6, '7'); window.open('https://www.patreon.com/YukiArimo', '_blank'); }, + 'Y': () => callYuna[document.getElementById('videoCallModal').classList.contains('show') ? 'hide' : 'show']() }; - if (keyMap[event.key]) { - event.preventDefault(); - keyMap[event.key](); - } + if (keyMap[event.key]) { event.preventDefault(); keyMap[event.key](); } } - - if (event.key === "Enter" && activeElement.id === 'input_text') { - event.preventDefault(); - messageManagerInstance.sendMessage(''); + if (event.key === "Enter" && document.activeElement.id === 'input_text') { + event.preventDefault(); messageManagerInstance.sendMessage(''); } }); const activateTab = (index, tabId) => { - const navSidebar = document.getElementsByClassName('side-link'); - [...navSidebar].forEach((link, idx) => link.classList.toggle('active', idx === index)); + [...document.getElementsByClassName('side-link')].forEach((l, i) => l.classList.toggle('active', i === index)); if (tabId) OpenTab(tabId); }; @@ -142,7 +117,6 @@ const callYuna = { // Sidebar and Tabs const navSidebar = document.getElementsByClassName('side-link'); const scrollToTop = document.querySelector('.scroll-to-top'); - [...navSidebar].forEach((link, i) => { link.addEventListener('click', () => { scrollToTop.style.display = i === 0 ? 'none' : 'flex'; @@ -150,9 +124,8 @@ const scrollToTop = document.querySelector('.scroll-to-top'); link.classList.add('active'); }); }); - document.querySelectorAll('.side-link')[3]?.addEventListener('click', () => { - ['character', 'system'].forEach(id => { + ['character','system'].forEach(id => { const el = document.getElementById(id); if (el) el.style.height = `calc(${el.scrollHeight}px + 3px)`; }); @@ -161,11 +134,250 @@ document.querySelectorAll('.side-link')[3]?.addEventListener('click', () => { // Apply Settings const applySettings = () => { const { settings } = JSON.parse(localStorage.getItem('config')) || {}; - if (!settings) return; - - settings.streaming && document.querySelector('#streamingChatMode')?.click(); - settings.customConfig && document.querySelector('#customConfig')?.click(); - settings.sounds && document.querySelector('#soundsMode')?.click(); + if (settings) { + settings.streaming && document.querySelector('#streamingChatMode')?.click(); + settings.customConfig && document.querySelector('#customConfig')?.click(); + settings.sounds && document.querySelector('#soundsMode')?.click(); + } }; +setTimeout(applySettings, 100); + +class NotificationManager { + constructor() { + this.dropdownMenu = document.querySelector('#notifications-container'); + this.messages = []; + } + + add(message) { + this.messages.push(message); + this.render(); + } + + delete(message) { + this.messages = this.messages.filter(msg => msg !== message); + this.render(); + } + + clearAll() { + this.messages = []; + this.render(); + } + + render() { + this.dropdownMenu.innerHTML = this.messages.map(message => ` + +
+ ${message} +

Push Notifications Demo

+ + + +
+

Send Test Notification

+
+
+ +
+
+
+ `).join(''); + } +} + +// Create an instance of NotificationManager +const notificationManagerInstance = new NotificationManager(); + +// Call the add method +notificationManagerInstance.add("Hello! Welcome to the chat room!"); + +function updateMsgCount() { + setTimeout(() => { + document.getElementById('messageCount').textContent = document.querySelectorAll('#message-container .p-2.mb-2').length; + document.getElementById('chatsCount').textContent = document.querySelectorAll('#chat-items .collection-item').length; + }, 300); +} + +function deleteMessageFromHistory(message) { + let fileName = selectedFilename; + + fetch(`${config_data.server.url + config_data.server.port}/history`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + chat: fileName, + task: "delete_message", + text: message + }) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(historyManagerInstance.loadSelectedHistory(fileName)) + .catch(error => { + console.error('An error occurred:', error); + }); +} + +// Function to adjust textarea height +function adjustTextareaHeight(textarea) { + textarea.style.height = 'auto'; + textarea.style.height = textarea.scrollHeight + 'px'; +} + +// Function to initialize all textareas +function initializeTextareas() { + const textareas = document.querySelectorAll('textarea'); + textareas.forEach(textarea => { + // Disable manual resizing + textarea.style.resize = 'none'; + + // Initial adjustment + adjustTextareaHeight(textarea); + + // Add event listeners for real-time updates + textarea.addEventListener('input', () => adjustTextareaHeight(textarea)); + textarea.addEventListener('change', () => adjustTextareaHeight(textarea)); + + // Create a MutationObserver to watch for changes in content + const observer = new MutationObserver(() => adjustTextareaHeight(textarea)); + observer.observe(textarea, { + childList: true, + characterData: true, + subtree: true + }); + }); +} + +// Run the initialization when the DOM is fully loaded +document.addEventListener('DOMContentLoaded', initializeTextareas); + +var vapidPublicKey = 'BLAWDkBakXLWfyQP5zAXR5Dyv4-W1nsRDkUk9Kw9MqKppQCdbsP-yfz7kEpAPvDMy2lszg_SZ9QEC9Uda8mpKSg' + +// static/app.js +let swRegistration = null; + +// Check if service workers are supported +if ('serviceWorker' in navigator && 'PushManager' in window) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/static/sw.js') + .then(registration => { + swRegistration = registration; + initializeUI(); + }) + .catch(error => console.error('Service Worker Error:', error)); + }); +} + +function initializeUI() { + const subscribeButton = document.getElementById('subscribe'); + const unsubscribeButton = document.getElementById('unsubscribe'); + + // Check for existing subscription + swRegistration.pushManager.getSubscription() + .then(subscription => { + if (subscription) { + console.log('User is already subscribed:', subscription); + updateSubscriptionOnServer(subscription); + subscribeButton.disabled = true; + unsubscribeButton.disabled = false; + } else { + subscribeButton.disabled = false; + unsubscribeButton.disabled = true; + } + }); + + // Set button click events + subscribeButton.addEventListener('click', subscribeUser); + unsubscribeButton.addEventListener('click', unsubscribeUser); +} + +function subscribeUser() { + const applicationServerKey = urlB64ToUint8Array(vapidPublicKey); + + swRegistration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }) + .then(subscription => { + console.log('User is subscribed:', subscription); + updateSubscriptionOnServer(subscription); + updateButtonState(); + }) + .catch(err => console.error('Failed to subscribe user: ', err)); +} + +function unsubscribeUser() { + swRegistration.pushManager.getSubscription() + .then(subscription => { + if (subscription) { + return subscription.unsubscribe(); + } + }) + .then(() => { + updateButtonState(); + console.log('User is unsubscribed'); + }) + .catch(error => console.error('Error unsubscribing', error)); +} + +function updateSubscriptionOnServer(subscription) { + fetch('/subscribe', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(subscription) + }); +} + +function sendNotification() { + const title = document.getElementById('title').value; + const body = document.getElementById('body').value; + + fetch('/send-notification', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + title: title, + body: body + }) + }); +} + +// Helper function to convert VAPID key +function urlB64ToUint8Array(base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} -applySettings(); \ No newline at end of file +function updateButtonState() { + const subscribeButton = document.getElementById('subscribe'); + const unsubscribeButton = document.getElementById('unsubscribe'); + swRegistration.pushManager.getSubscription() + .then(subscription => { + if (subscription) { + subscribeButton.disabled = true; + unsubscribeButton.disabled = false; + } else { + subscribeButton.disabled = false; + unsubscribeButton.disabled = true; + } + }); +} \ No newline at end of file diff --git a/static/manifest.json b/static/manifest.json index af9f778..e502429 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -1,7 +1,7 @@ { - "short_name": "Yuna AI", - "name": "Yuna AI", - "description": "Yuna AI - Your Private Companion", + "short_name": "Yuna Ai", + "name": "Yuna Ai", + "description": "Yuna Ai - Your Private Companion", "icons": [{ "src": "/static/img/yuna-ai.png", "sizes": "512X512", diff --git a/static/sw.js b/static/sw.js index cf9ae08..5bc6487 100644 --- a/static/sw.js +++ b/static/sw.js @@ -30,7 +30,7 @@ self.addEventListener('push', function(event) { payload = event.data.json(); } catch (e) { payload = { - title: 'Yuna AI', + title: 'Yuna Ai', body: event.data ? event.data.text() : 'No payload' }; } @@ -42,12 +42,12 @@ self.addEventListener('push', function(event) { data: { dateOfArrival: Date.now(), primaryKey: 1, - url: 'https://yuki.yuna-ai.pro' + url: 'https://www.yuna-ai.com' } }; event.waitUntil( - self.registration.showNotification(payload.title || 'Yuna AI', options) + self.registration.showNotification(payload.title || 'Yuna Ai', options) ); }); diff --git a/yuna.html b/yuna.html index 18c2788..9eafdb2 100644 --- a/yuna.html +++ b/yuna.html @@ -5,27 +5,28 @@ - Yuna AI + Yuna Ai - + - + - + + @@ -79,9 +80,9 @@