import { appModel } from './model.js'; import {utils} from './utils.js'; import {controller } from './controller.js' const view = { elem: { currentInputElem: null, voiceButton: null, }, recorder: null, async init() { this.recorder = new utils.Recorder(); this.elem.voiceButton = this.createButton(); appModel.keepButtonAliveInterval = setInterval(() => { const whisperButton = document.getElementById("whisper_voice_button"); if (!whisperButton) { this.createButton(); } }, 2000); }, createButton() { if (document.getElementById("whisper_voice_button")) { return; } let button = document.createElement("button"); this.elem.voiceButton = button; button.id = appModel.voice_button_id; button.innerText = "◯"; button.type = "button"; button.classList.add("speech-to-text-button"); button.style.top = window.innerHeight - 100 + "px"; button.style.left = "0"; button.style.width = "40px"; button.style.height = button.style.width; button.style.fontSize = "30px"; button.style.padding = "0"; button.style.border = "0px"; button.style.color = "blue"; button.style.background = "transparent"; button.style.zIndex = 1000000; button.style.position = "fixed"; button.style.borderRadius = "50%"; button.style.userSelect = "none"; button.style.touchAction = "none"; document.body.appendChild(button); utils.dragElement(button, button); button.addEventListener("click", () => { console.log("createButton():clicked"); }); button.addEventListener("pointerdown", async (event) => { event.preventDefault(); controller.handleRecording(event); }); button.addEventListener("pointerup", () => { console.log("createButton pointerup"); controller.stopRecording(); }); utils.addEventListenerForActualClick(button, (event) => { let clientX = event?.clientX; let clientY = event?.clientY; this.createMenu(clientX + 50, clientY + 50); }); utils.addEventListenerForActualClick(document.body, (event) => { if (view.recorder.isRecording) return; if (event.target.tagName === "INPUT" && appModel.supportedInputTypeList.includes(event.target.type)) { utils.moveElementNearMouse(button, event.target, true, event); } else if (event.target.tagName === "TEXTAREA" || utils.isEditableElement(event.target)) { utils.moveElementNearMouse(button, event.target, true, event); } }); window.addEventListener("resize", () => { let buttonPos = button.getBoundingClientRect(); if (buttonPos.top > window.innerHeight - buttonPos.height) { button.style.top = window.innerHeight - buttonPos.height + "px"; } }); return button; }, createMenu(x, y, id = "webai_input_menu") { const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; let menuContainer = document.getElementById(id); if (menuContainer) { menuContainer.style.left = Math.min(x, windowWidth - menuContainer.offsetWidth * 0.5) + "px"; menuContainer.style.top = Math.min(y, windowHeight - menuContainer.offsetHeight) - 100 + "px"; menuContainer.style.zIndex = "99999"; return; } menuContainer = document.createElement("div"); document.body.appendChild(menuContainer); menuContainer.id = id; menuContainer.style.zIndex = "99999"; menuContainer.style.position = "fixed"; menuContainer.style.backgroundColor = "white"; menuContainer.style.boxShadow = "0 2px 5px rgba(0, 0, 0, 0.1)"; menuContainer.style.borderRadius = "4px"; menuContainer.style.display = "flex"; menuContainer.style.flexDirection = "column"; menuContainer.style.alignItems = "flex-start"; menuContainer.style.padding = "10px"; menuContainer.style.left = Math.min(x, windowWidth - menuContainer.offsetWidth) + "px"; menuContainer.style.top = Math.min(y, windowHeight - menuContainer.offsetHeight) - 100 + "px"; menuContainer.style.opacity = '0.7' utils.disableSelect(menuContainer); menuContainer.style.maxHeight = "60vh"; menuContainer.style.overflowY = "auto"; menuContainer.style.cssText += ` scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, 0.3) transparent; `; menuContainer.style.msOverflowStyle = "none"; function createMenuItem(textContent, handler) { const menuItem = document.createElement("button"); utils.makeButtonFeedback(menuItem); menuContainer.appendChild(menuItem); menuItem.style.cssText = ` background-color: white; border: none; font-size: 14px; width: 80px; text-align: left; cursor: pointer; margin-bottom: 0; margin-top: 0; padding: 5px 10px; color: #333; transition: background-color 0.3s ease; display: flex; align-items: center; justify-content: flex-start; border-radius: 4px; `; menuItem.textContent = textContent; menuItem.addEventListener("pointerdown", (event) => { event.preventDefault(); if (handler) { handler(); } }); return menuItem; } const removeMenuItem = createMenuItem("Remove Menu"); removeMenuItem.addEventListener("pointerdown", () => menuContainer.remove() ); menuContainer.appendChild(removeMenuItem); const closeButton = createMenuItem("Close"); closeButton.addEventListener("pointerdown", () => { if (confirm("remove the AI tool now?")) { clearInterval(appModel.keepButtonAliveInterval); view.elem.voiceButton.remove(); menuContainer.remove(); } }); menuContainer.appendChild(closeButton); createMenuItem("TTS", () => { let currentLineString = utils.getCurrentLineString(document.activeElement); let selectText = window.getSelection().toString(); let ttstext=selectText.length >= 1 ? selectText : currentLineString; utils.tts( ttstext, "de-DE-SeraphinaMultilingualNeural" ); }); const startMenuItem = createMenuItem("Start"); menuContainer.appendChild(startMenuItem); startMenuItem.addEventListener("pointerdown", () => { view.elem.voiceButton.style.top = '0px'; view.elem.voiceButton.style.left = window.innerWidth * 0.8 + 'px'; appModel.isRecording = true; controller.startRecordingWithSilenceDetection(); }); let copyButton = createMenuItem("Copy"); menuContainer.appendChild(copyButton); copyButton.addEventListener("pointerdown", (e) => { e.preventDefault(); if(window.getSelection().toString().length > 0){ document.execCommand("copy") ; } else{ utils.copyToClipboard(utils.getCurrentLineString(document.activeElement)); } utils.showToast("Copied to clipboard"); }); createMenuItem("Cut", () => { document.execCommand("copy"); document.execCommand("delete"); utils.showToast("Cut to clipboard"); }); let pasteButton = createMenuItem("Paste"); menuContainer.appendChild(pasteButton); pasteButton.addEventListener("pointerdown", async (e) => { e.preventDefault(); try { const text = await navigator.clipboard.readText(); utils.writeText(document.activeElement, text); console.log("Clipboard text:", text); } catch (err) { console.error("Clipboard access denied:", err); } }); let enterButton = createMenuItem("Enter"); menuContainer.appendChild(enterButton); enterButton.addEventListener("pointerdown", (e) => { e.preventDefault(); document.execCommand("insertText", false, "\n"); }); createMenuItem("Correct", () => { let correctPrompt = 'correct mistakes of the text, put the corrected text in the codeblock:\n '; controller.chat(correctPrompt); }); let askButton = createMenuItem("Ask"); askButton.style.touchAction='none'; menuContainer.appendChild(askButton); askButton.addEventListener("pointerdown", (e) => { e.preventDefault(); controller.ask(); document.body.addEventListener("pointerup", () => { controller.stopRecording(); }, { once: true }); }); menuContainer.style.left = Math.min(x, windowWidth - menuContainer.offsetWidth * 0.5) + "px"; menuContainer.style.top = Math.min(y, windowHeight - menuContainer.offsetHeight) - 100 + "px"; document.body.appendChild(menuContainer); }, }; export { view };