|
|
|
|
|
const utils = { |
|
|
Recorder: class Recorder { |
|
|
constructor() { |
|
|
this.isRecording = false; |
|
|
this.mediaRecorder; |
|
|
this.encodeType = "audio/mpeg"; |
|
|
this.language = "en"; |
|
|
this.recordingColor = "lightblue"; |
|
|
this.autoStop=true; |
|
|
} |
|
|
|
|
|
async startRecording( |
|
|
targetElement, |
|
|
silenceHandler = () => { |
|
|
console.log("silence detect"); |
|
|
}, |
|
|
autoStop = true |
|
|
) { |
|
|
targetElement = targetElement || document.querySelector(`#whisper_voice_button`); |
|
|
|
|
|
this.stopRecording(); |
|
|
console.log("start recording"); |
|
|
return navigator.mediaDevices |
|
|
.getUserMedia({ audio: true }) |
|
|
.then((stream) => { |
|
|
this.mediaRecorder = new MediaRecorder(stream); |
|
|
|
|
|
let silenceStart = Date.now(); |
|
|
let silenceDuration = 0; |
|
|
|
|
|
let mediaRecorder = this.mediaRecorder; |
|
|
let audioChunks = []; |
|
|
mediaRecorder.start(); |
|
|
this.isRecording = true; |
|
|
targetElement.style.backgroundColor = "rgba(173, 216, 230, 0.3)"; |
|
|
|
|
|
let volumeInterval; |
|
|
let audioContext; |
|
|
audioContext = new AudioContext(); |
|
|
const analyser = audioContext.createAnalyser(); |
|
|
const microphone = audioContext.createMediaStreamSource( |
|
|
mediaRecorder.stream |
|
|
); |
|
|
microphone.connect(analyser); |
|
|
analyser.fftSize = 512; |
|
|
const bufferLength = analyser.frequencyBinCount; |
|
|
const dataArray = new Uint8Array(bufferLength); |
|
|
|
|
|
const updateButtonFontSize = () => { |
|
|
analyser.getByteFrequencyData(dataArray); |
|
|
let sum = 0; |
|
|
for (let i = 0; i < bufferLength; i++) { |
|
|
sum += dataArray[i]; |
|
|
} |
|
|
let averageVolume = sum / bufferLength; |
|
|
if (averageVolume < 10) { |
|
|
silenceDuration = Date.now() - silenceStart; |
|
|
if (silenceDuration > 1000) { |
|
|
silenceHandler(); |
|
|
} |
|
|
} else { |
|
|
silenceStart = Date.now(); |
|
|
} |
|
|
|
|
|
let scale = 3 + averageVolume / 15; |
|
|
targetElement.style.transform = `scale(${scale})`; |
|
|
}; |
|
|
|
|
|
volumeInterval = setInterval(updateButtonFontSize, 100); |
|
|
|
|
|
mediaRecorder.addEventListener("dataavailable", (event) => { |
|
|
console.log("dataavailable"); |
|
|
audioChunks.push(event.data); |
|
|
}); |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
mediaRecorder.addEventListener("stop", async () => { |
|
|
this.isRecording = false; |
|
|
console.log("stop"); |
|
|
clearInterval(volumeInterval); |
|
|
const audioBlob = new Blob(audioChunks, { |
|
|
type: this.encodeType, |
|
|
}); |
|
|
targetElement.style.transform = `scale(1)`; |
|
|
targetElement.style.background = "transparent"; |
|
|
|
|
|
audioContext?.close(); |
|
|
mediaRecorder.stream.getTracks().forEach((track) => track.stop()); |
|
|
console.log("resolved "); |
|
|
resolve(audioBlob); |
|
|
}); |
|
|
}); |
|
|
}) |
|
|
.catch((error) => { |
|
|
if ( |
|
|
error.name === "PermissionDeniedError" || |
|
|
error.name === "NotAllowedError" |
|
|
) { |
|
|
console.error("User denied permission to access audio"); |
|
|
console.log("Audio permission denied"); |
|
|
} else { |
|
|
console.error( |
|
|
"An error occurred while accessing the audio device", |
|
|
error |
|
|
); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
async startRecordingWithSilenceDetection( |
|
|
targetElement,silenceHandler = () => console.log("silence detect")) { |
|
|
|
|
|
let autoStop = this.autoStop || true; |
|
|
this.stopRecording(); |
|
|
console.log("start recording"); |
|
|
return navigator.mediaDevices |
|
|
.getUserMedia({ audio: true }) |
|
|
.then((stream) => { |
|
|
this.mediaRecorder = new MediaRecorder(stream); |
|
|
|
|
|
let startTime = Date.now(); |
|
|
|
|
|
let isSilent = false; |
|
|
let isLongSilent = true; |
|
|
let silenceStart = Date.now(); |
|
|
let silenceDuration = 0; |
|
|
|
|
|
let mediaRecorder = this.mediaRecorder; |
|
|
let audioChunks = []; |
|
|
mediaRecorder.start(); |
|
|
this.isRecording = true; |
|
|
targetElement.style.backgroundColor = "rgba(173, 216, 230, 0.3)"; |
|
|
|
|
|
let volumeInterval; |
|
|
let audioContext; |
|
|
audioContext = new AudioContext(); |
|
|
const analyser = audioContext.createAnalyser(); |
|
|
const microphone = audioContext.createMediaStreamSource( |
|
|
mediaRecorder.stream |
|
|
); |
|
|
microphone.connect(analyser); |
|
|
analyser.fftSize = 512; |
|
|
const bufferLength = analyser.frequencyBinCount; |
|
|
const dataArray = new Uint8Array(bufferLength); |
|
|
|
|
|
const handleAudioData = () => { |
|
|
analyser.getByteFrequencyData(dataArray); |
|
|
let sum = 0; |
|
|
for (let i = 0; i < bufferLength; i++) { |
|
|
sum += dataArray[i]; |
|
|
} |
|
|
let averageVolume = sum / bufferLength; |
|
|
|
|
|
if (averageVolume < 15) { |
|
|
if (isSilent) { |
|
|
silenceDuration = Date.now() - silenceStart; |
|
|
if (silenceDuration > 3000) { |
|
|
isLongSilent = true; |
|
|
mediaRecorder.requestData(); |
|
|
silenceStart = Date.now(); |
|
|
} |
|
|
} else { |
|
|
silenceDuration = Date.now() - silenceStart; |
|
|
if (silenceDuration > 1000) { |
|
|
isSilent = true; |
|
|
console.log('change isSilent to true'); |
|
|
|
|
|
mediaRecorder.requestData(); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
isSilent = false; |
|
|
isLongSilent = false; |
|
|
silenceStart = Date.now(); |
|
|
} |
|
|
|
|
|
let scale = 3 + averageVolume / 15; |
|
|
targetElement.style.transform = `scale(${scale})`; |
|
|
}; |
|
|
volumeInterval = setInterval(handleAudioData, 100); |
|
|
|
|
|
let counter = 0; |
|
|
let firstdata; |
|
|
setTimeout(() => { |
|
|
mediaRecorder.requestData(); |
|
|
}, 200); |
|
|
mediaRecorder.addEventListener("dataavailable", (event) => { |
|
|
if (autoStop === true) { |
|
|
if (Date.now() - startTime > 600000) { |
|
|
mediaRecorder.stop(); |
|
|
} |
|
|
} |
|
|
counter++; |
|
|
if (counter <= 1) { |
|
|
firstdata = event.data; |
|
|
if (event.data.size > 0) { |
|
|
audioChunks.push(event.data); |
|
|
} |
|
|
return; |
|
|
} |
|
|
console.log("dataavailable", event.data); |
|
|
if (isLongSilent) { |
|
|
console.log("dataavailable,Long silent will do noting", event.data); |
|
|
return; |
|
|
} |
|
|
|
|
|
silenceHandler(new Blob([firstdata, event.data], { type: mediaRecorder.mimeType })); |
|
|
}); |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
mediaRecorder.addEventListener("stop", async () => { |
|
|
this.isRecording = false; |
|
|
console.log("stop"); |
|
|
clearInterval(volumeInterval); |
|
|
const audioBlob = new Blob(audioChunks, { |
|
|
type: this.encodeType, |
|
|
}); |
|
|
targetElement.style.transform = `scale(1)`; |
|
|
targetElement.style.background = "transparent"; |
|
|
|
|
|
audioContext?.close(); |
|
|
mediaRecorder.stream.getTracks().forEach((track) => track.stop()); |
|
|
console.log("resolved "); |
|
|
resolve(audioBlob); |
|
|
}); |
|
|
}); |
|
|
}) |
|
|
.catch((error) => { |
|
|
if ( |
|
|
error.name === "PermissionDeniedError" || |
|
|
error.name === "NotAllowedError" |
|
|
) { |
|
|
console.error("User denied permission to access audio"); |
|
|
showNotification("Audio permission denied"); |
|
|
} else { |
|
|
console.error( |
|
|
"An error occurred while accessing the audio device", |
|
|
error |
|
|
); |
|
|
showNotification("Error accessing audio device"); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
stopRecording() { |
|
|
this.isRecording = false; |
|
|
|
|
|
this.mediaRecorder?.stop(); |
|
|
this.mediaRecorder?.audioContext?.close(); |
|
|
this.mediaRecorder?.stream?.getTracks().forEach((track) => track.stop()); |
|
|
} |
|
|
}, |
|
|
tts: function synthesizeSpeech(text = 'test text', voice = 'alloy') { |
|
|
let url = `https://tts.grapee.ddnsfree.com/tts/${encodeURIComponent(text)}`; |
|
|
|
|
|
let container = document.getElementById("devlent_tts_container"); |
|
|
if (!container) { |
|
|
container = document.createElement("div"); |
|
|
container.id = "devlent_tts_container"; |
|
|
document.body.appendChild(container); |
|
|
} |
|
|
|
|
|
let audio = document.getElementById("tts_audio"); |
|
|
if (!audio) { |
|
|
audio = document.createElement("audio"); |
|
|
audio.id = "tts_audio"; |
|
|
container.appendChild(audio); |
|
|
|
|
|
let button = document.createElement("button"); |
|
|
button.innerHTML = "x"; |
|
|
button.style.backgroundColor = 'transparent'; |
|
|
button.style.marginLeft = '10px'; |
|
|
|
|
|
button.addEventListener('pointerdown', () => container.style.display = "none") |
|
|
let br = document.createElement('br'); |
|
|
|
|
|
container.prepend(br); |
|
|
container.prepend(button); |
|
|
} |
|
|
|
|
|
container.style.display = "block"; |
|
|
container.style.position = "fixed"; |
|
|
container.style.bottom = "0"; |
|
|
container.style.right = "0"; |
|
|
container.style.height = 'fit-content' |
|
|
|
|
|
if (!text) return; |
|
|
audio.src = url; |
|
|
audio.controls = true; |
|
|
audio.autoplay = true; |
|
|
|
|
|
utils.dragElement(container, container) |
|
|
}, |
|
|
stt: async (audioBlob) => { |
|
|
const formData = new FormData(); |
|
|
formData.append("file", audioBlob, "audio.mp3"); |
|
|
formData.append("model", "whisper-large-v3"); |
|
|
|
|
|
try { |
|
|
const response = await fetch('/openai/v1/audio/transcriptions', { |
|
|
method: 'POST', |
|
|
body: formData |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`Error: ${response.status} ${response.statusText}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
return data; |
|
|
} catch (error) { |
|
|
console.error('There was an error with the transcription request:', error); |
|
|
} |
|
|
|
|
|
|
|
|
}, |
|
|
|
|
|
checkValidString(str) { |
|
|
if (str === undefined || str === null || str.trim() === "") { |
|
|
return false; |
|
|
} |
|
|
if (str === "undefined" || str === "null") { |
|
|
return false; |
|
|
} |
|
|
return true; |
|
|
}, |
|
|
getCurrentLineString(element) { |
|
|
const selection = window.getSelection(); |
|
|
const range = selection.getRangeAt(0); |
|
|
const node = range.startContainer; |
|
|
const offset = range.startOffset; |
|
|
|
|
|
if (node.nodeType === Node.TEXT_NODE) { |
|
|
const text = node.textContent; |
|
|
const lineStart = text.lastIndexOf('\n', offset) + 1; |
|
|
const lineEnd = text.indexOf('\n', offset); |
|
|
const line = lineEnd === -1 ? text.slice(lineStart) : text.slice(lineStart, lineEnd); |
|
|
return line; |
|
|
} |
|
|
|
|
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); |
|
|
let currentNode, currentLine = ''; |
|
|
|
|
|
while (currentNode = walker.nextNode()) { |
|
|
const text = currentNode.textContent; |
|
|
const lines = text.split('\n'); |
|
|
|
|
|
for (let i = 0; i < lines.length; i++) { |
|
|
if (range.intersectsNode(currentNode)) { |
|
|
currentLine = lines[i]; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
if (currentLine !== '') { |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
return currentLine; |
|
|
}, |
|
|
isEditableElement: function isEditableElement(element) { |
|
|
while (element) { |
|
|
if (element.contentEditable === "true") { |
|
|
return true; |
|
|
} |
|
|
element = element.parentElement; |
|
|
} |
|
|
return false; |
|
|
}, |
|
|
disableSelect: function disableSelect(element) { |
|
|
element.style.userSelect = "none"; |
|
|
|
|
|
element.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
}); |
|
|
}, |
|
|
getSelectionText: function getSelectionText() { |
|
|
let activeElement = document.activeElement; |
|
|
if (activeElement && activeElement.value) { |
|
|
return activeElement.value.substring( |
|
|
activeElement.selectionStart, |
|
|
activeElement.selectionEnd |
|
|
); |
|
|
} else { |
|
|
return window.getSelection().toString(); |
|
|
} |
|
|
}, |
|
|
makeButtonFeedback: function makeButtonFeedback(button) { |
|
|
let originalColor = button.style.backgroundColor || "white"; |
|
|
|
|
|
button.addEventListener("pointerdown", function () { |
|
|
button.style.backgroundColor = "lightblue"; |
|
|
}); |
|
|
button.addEventListener("pointerup", function () { |
|
|
setTimeout(() => { button.style.backgroundColor = originalColor; }, 1000) |
|
|
}); |
|
|
|
|
|
button.addEventListener("pointercancel", function () { |
|
|
setTimeout(() => { button.style.backgroundColor = originalColor; }, 1000) |
|
|
}); |
|
|
}, |
|
|
showToast: function showToast( |
|
|
text, |
|
|
x = 0, |
|
|
y = 0, |
|
|
w = 200, |
|
|
h = 0, |
|
|
duration = 1000, |
|
|
zIndex = 9999 |
|
|
) { |
|
|
const textArea = document.createElement("textarea"); |
|
|
textArea.value = text; |
|
|
textArea.style.width = w + "px"; |
|
|
textArea.style.height = h === 0 ? "auto" : h + "px"; |
|
|
textArea.style.borderWidth = "0"; |
|
|
textArea.style.outline = "none"; |
|
|
textArea.style.position = "fixed"; |
|
|
textArea.style.left = x + "px"; |
|
|
textArea.style.top = y + "px"; |
|
|
textArea.style.zIndex = zIndex; |
|
|
textArea.disabled = true; |
|
|
document.body.appendChild(textArea); |
|
|
setTimeout(() => { |
|
|
document.body.removeChild(textArea); |
|
|
}, duration); |
|
|
}, |
|
|
copyToClipboard: function copyToClipboard(text) { |
|
|
const textArea = document.createElement("textarea"); |
|
|
textArea.value = text; |
|
|
textArea.style.width = "50%"; |
|
|
textArea.style.height = "100px"; |
|
|
textArea.style.borderWidth = "0"; |
|
|
textArea.style.outline = "none"; |
|
|
textArea.style.position = "fixed"; |
|
|
textArea.style.left = "0"; |
|
|
textArea.style.top = "0"; |
|
|
textArea.style.zIndex = "9999999"; |
|
|
document.body.appendChild(textArea); |
|
|
textArea.select(); |
|
|
document.execCommand("copy"); |
|
|
textArea.disabled = true; |
|
|
textArea.value = "copyed to clipboard \n" + textArea.value; |
|
|
textArea.scrollTo(10000, 100000); |
|
|
setTimeout(() => { |
|
|
document.body.removeChild(textArea); |
|
|
}, 4000); |
|
|
}, |
|
|
|
|
|
writeText: function writeText(targetElement, text, prefix = " ", endfix = " ") { |
|
|
console.log("writeText(): ", targetElement); |
|
|
document.execCommand("insertText", false, `${prefix}${text}${endfix}`) || utils.copyToClipboard(text); |
|
|
}, |
|
|
dragElement: function dragElement(elmnt, movableElmnt = elmnt.parentElement, speed = 1) { |
|
|
elmnt.style.touchAction = "none"; |
|
|
let pos1 = 0, |
|
|
pos2 = 0, |
|
|
pos3 = 0, |
|
|
pos4 = 0; |
|
|
|
|
|
let rmShadeTimeout; |
|
|
let shadeDiv; |
|
|
|
|
|
elmnt.addEventListener("pointerdown", (e) => { |
|
|
dragMouseDown(e); |
|
|
}); |
|
|
|
|
|
function dragMouseDown(e) { |
|
|
e = e || window.event; |
|
|
e.preventDefault(); |
|
|
|
|
|
pos3 = e.clientX; |
|
|
pos4 = e.clientY; |
|
|
document.body.addEventListener("pointermove", elementDrag); |
|
|
document.body.addEventListener("pointerup", closeDragElement); |
|
|
|
|
|
shadeDiv = |
|
|
document.querySelector("#shadeDivForDragElement") || |
|
|
document.createElement("div"); |
|
|
shadeDiv.id = "shadeDivForDragElement"; |
|
|
shadeDiv.style.width = "300vw"; |
|
|
shadeDiv.style.height = "300vh"; |
|
|
shadeDiv.style.position = "fixed"; |
|
|
shadeDiv.style.top = "0"; |
|
|
shadeDiv.style.left = "0"; |
|
|
shadeDiv.style.backgroundColor = "rgb(230,230,230,0.2)"; |
|
|
shadeDiv.style.zIndex = 100000; |
|
|
document.body.appendChild(shadeDiv); |
|
|
rmShadeTimeout = setTimeout(() => { |
|
|
let shadeDiv = document.querySelector("#shadeDivForDragElement"); |
|
|
shadeDiv && document.body.removeChild(shadeDiv); |
|
|
}, 10000); |
|
|
} |
|
|
|
|
|
function elementDrag(e) { |
|
|
e = e || window.event; |
|
|
e.preventDefault(); |
|
|
pos1 = pos3 - e.clientX; |
|
|
pos2 = pos4 - e.clientY; |
|
|
|
|
|
movableElmnt.style.position = "fixed"; |
|
|
movableElmnt.style.top = e.clientY - elmnt.clientHeight / 2 + "px"; |
|
|
movableElmnt.style.left = e.clientX - elmnt.clientWidth / 2 + "px"; |
|
|
} |
|
|
|
|
|
function closeDragElement() { |
|
|
console.log("closeDragElement(): pointerup"); |
|
|
document.body.removeEventListener("pointermove", elementDrag); |
|
|
document.body.removeEventListener("pointerup", closeDragElement); |
|
|
|
|
|
document.body.removeChild( |
|
|
document.querySelector("#shadeDivForDragElement") |
|
|
); |
|
|
} |
|
|
}, |
|
|
|
|
|
renderMarkdown(mdString, targetElement) { |
|
|
let headerPattern = /^(#{1,6})\s*(.*)$/gm; |
|
|
const boldPattern = /\*\*(.*?)\*\*/g; |
|
|
const linkPattern = /\[(.*?)\]\((.*?)\)/g; |
|
|
const newlinePattern = /(?:\n)/g; |
|
|
const inlineCodePattern = /`(.*?)`/g; |
|
|
const codeBlockPattern = /```(\w+)?\n(.*?)```/gs; |
|
|
|
|
|
let html = mdString; |
|
|
|
|
|
let parts = html.split("```"); |
|
|
for (let i = 0; i < parts.length; i++) { |
|
|
if (i % 2 === 0) { |
|
|
parts[i] = parts[i].replace(headerPattern, (match, hash, content) => { |
|
|
const level = hash.length; |
|
|
return `<h${level}>${content}</h${level}>`; |
|
|
}); |
|
|
parts[i] = parts[i].replace(newlinePattern, (match, hash, content) => { |
|
|
const level = hash.length; |
|
|
return `<br>`; |
|
|
}); |
|
|
} |
|
|
} |
|
|
html = parts.join("```"); |
|
|
|
|
|
html = html.replace(boldPattern, "<strong>$1</strong>"); |
|
|
html = html.replace(linkPattern, '<a href="$2">$1</a>'); |
|
|
|
|
|
html = html.replace(codeBlockPattern, (match, language, code) => { |
|
|
return ` |
|
|
<div class="code-block"> |
|
|
<button class="copy-code-btn">Copy</button> |
|
|
<button class="insert-code-btn">Insert</button> |
|
|
<pre id='devilentCodePre'><xmp>${code}</xmp></pre> |
|
|
</div> |
|
|
`; |
|
|
}); |
|
|
|
|
|
html = html.replace(inlineCodePattern, "<code>$1</code>"); |
|
|
|
|
|
targetElement.innerHTML = html; |
|
|
|
|
|
const buttons = targetElement.querySelectorAll(".code-block button"); |
|
|
buttons.forEach((btn) => { |
|
|
btn.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const code = btn.parentElement.querySelector("pre").innerText; |
|
|
if (btn.classList.contains("copy-code-btn")) { |
|
|
utils.copyToClipboard(code); |
|
|
} else if (btn.classList.contains("insert-code-btn")) { |
|
|
console.log("insert button down"); |
|
|
utils.writeText(document.activeElement, code, "", ""); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
const copyButton = document.createElement("button"); |
|
|
copyButton.innerText = "Copy"; |
|
|
copyButton.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
e.stopPropagation(); |
|
|
|
|
|
utils.copyToClipboard(mdString); |
|
|
}); |
|
|
|
|
|
const insertButton = document.createElement("button"); |
|
|
insertButton.innerText = "Insert"; |
|
|
insertButton.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
e.stopPropagation(); |
|
|
|
|
|
utils.writeText(document.activeElement, mdString, "", ""); |
|
|
}); |
|
|
copyButton.classList.add("copy-btn"); |
|
|
insertButton.classList.add("insert-btn"); |
|
|
|
|
|
let editButton = document.createElement("button"); |
|
|
editButton.innerText = "Edit"; |
|
|
editButton.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
if (targetElement.isEditableElement) { |
|
|
targetElement.setAttribute('contenteditable', 'false'); |
|
|
} |
|
|
else { |
|
|
targetElement.setAttribute('contenteditable', 'true'); |
|
|
} |
|
|
}); |
|
|
editButton.classList.add("copy-btn"); |
|
|
|
|
|
const closeButton = document.createElement("button"); |
|
|
closeButton.innerText = "Close"; |
|
|
closeButton.addEventListener("pointerdown", (e) => { |
|
|
e.preventDefault(); |
|
|
targetElement.remove(); |
|
|
}); |
|
|
closeButton.classList.add("copy-btn"); |
|
|
|
|
|
let buttonContainer = document.createElement("div"); |
|
|
buttonContainer.classList.add("button-container"); |
|
|
|
|
|
buttonContainer.appendChild(copyButton); |
|
|
buttonContainer.appendChild(insertButton); |
|
|
buttonContainer.appendChild(closeButton); |
|
|
buttonContainer.appendChild(editButton); |
|
|
|
|
|
const parentElement = targetElement; |
|
|
|
|
|
buttonContainer.style.width = "100%"; |
|
|
buttonContainer.style.backgroundColor = parentElement.style.backgroundColor; |
|
|
buttonContainer.style.color = |
|
|
"lighten(" + buttonContainer.style.backgroundColor + ", 20%)"; |
|
|
|
|
|
targetElement.prepend(buttonContainer); |
|
|
utils.dragElement(buttonContainer, targetElement); |
|
|
|
|
|
targetElement.classList.add("markdown-container"); |
|
|
let markdownContainers = |
|
|
document.getElementsByClassName("markdown-container"); |
|
|
|
|
|
for (let i = 0; i < markdownContainers.length; i++) { |
|
|
markdownContainers[i].style.fontFamily = "Arial, sans-serif"; |
|
|
markdownContainers[i].style.lineHeight = "1.6"; |
|
|
markdownContainers[i].style.maxWidth = "800px"; |
|
|
markdownContainers[i].style.margin = "0 auto"; |
|
|
markdownContainers[i].style.padding = "0px"; |
|
|
markdownContainers[i].style.backgroundColor = "azure"; |
|
|
markdownContainers[i].style.overflow = "auto"; |
|
|
markdownContainers[i].style.boxShadow = "0px 0px 50px rgba(0, 0, 0, 0.4)"; |
|
|
} |
|
|
|
|
|
let codeBlocks = document.getElementsByClassName("code-block"); |
|
|
for (let i = 0; i < codeBlocks.length; i++) { |
|
|
codeBlocks[i].style.position = "relative"; |
|
|
} |
|
|
|
|
|
let insertCodeBtns = document.getElementsByClassName("insert-code-btn"); |
|
|
let codecopyBtns = document.getElementsByClassName("copy-code-btn"); |
|
|
|
|
|
for (let i = 0; i < codecopyBtns.length; i++) { |
|
|
codecopyBtns[i].style.top = "0"; |
|
|
codecopyBtns[i].style.position = "absolute"; |
|
|
codecopyBtns[i].style.right = "0"; |
|
|
codecopyBtns[i].style.margin = "5px"; |
|
|
codecopyBtns[i].style.padding = "2px 5px"; |
|
|
codecopyBtns[i].style.fontSize = "12px"; |
|
|
codecopyBtns[i].style.border = "none"; |
|
|
codecopyBtns[i].style.borderRadius = "3px"; |
|
|
codecopyBtns[i].style.backgroundColor = "#007bff"; |
|
|
codecopyBtns[i].style.color = "white"; |
|
|
codecopyBtns[i].style.cursor = "pointer"; |
|
|
} |
|
|
|
|
|
for (let i = 0; i < insertCodeBtns.length; i++) { |
|
|
insertCodeBtns[i].style.position = "absolute"; |
|
|
insertCodeBtns[i].style.top = "0"; |
|
|
insertCodeBtns[i].style.right = "50px"; |
|
|
insertCodeBtns[i].style.margin = "5px"; |
|
|
insertCodeBtns[i].style.padding = "2px 5px"; |
|
|
insertCodeBtns[i].style.fontSize = "12px"; |
|
|
insertCodeBtns[i].style.border = "none"; |
|
|
insertCodeBtns[i].style.borderRadius = "3px"; |
|
|
insertCodeBtns[i].style.backgroundColor = "#007bff"; |
|
|
insertCodeBtns[i].style.color = "white"; |
|
|
insertCodeBtns[i].style.cursor = "pointer"; |
|
|
} |
|
|
|
|
|
let copyBtns = document.getElementsByClassName("copy-btn"); |
|
|
let insertBtns = document.getElementsByClassName("insert-btn"); |
|
|
|
|
|
for (let i = 0; i < copyBtns.length; i++) { |
|
|
copyBtns[i].style.margin = "5px"; |
|
|
copyBtns[i].style.padding = "2px 5px"; |
|
|
copyBtns[i].style.fontSize = "12px"; |
|
|
copyBtns[i].style.border = "none"; |
|
|
copyBtns[i].style.borderRadius = "3px"; |
|
|
copyBtns[i].style.backgroundColor = "#007bff"; |
|
|
copyBtns[i].style.color = "white"; |
|
|
copyBtns[i].style.cursor = "pointer"; |
|
|
} |
|
|
|
|
|
for (let i = 0; i < insertBtns.length; i++) { |
|
|
insertBtns[i].style.margin = "5px"; |
|
|
insertBtns[i].style.padding = "2px 5px"; |
|
|
insertBtns[i].style.fontSize = "12px"; |
|
|
insertBtns[i].style.border = "none"; |
|
|
insertBtns[i].style.borderRadius = "3px"; |
|
|
insertBtns[i].style.backgroundColor = "#007bff"; |
|
|
insertBtns[i].style.color = "white"; |
|
|
insertBtns[i].style.cursor = "pointer"; |
|
|
} |
|
|
|
|
|
let pres = targetElement.getElementsByTagName("pre"); |
|
|
for (let i = 0; i < pres.length; i++) { |
|
|
pres[i].style.backgroundColor = "#f7f7f7"; |
|
|
pres[i].style.borderRadius = "5px"; |
|
|
pres[i].style.padding = "10px"; |
|
|
pres[i].style.whiteSpace = "pre-wrap"; |
|
|
pres[i].style.wordBreak = "break-all"; |
|
|
} |
|
|
|
|
|
let codes = targetElement.getElementsByTagName("code"); |
|
|
for (let i = 0; i < codes.length; i++) { |
|
|
codes[i].style.backgroundColor = "#f1f1f1"; |
|
|
codes[i].style.borderRadius = "3px"; |
|
|
codes[i].style.padding = "2px 5px"; |
|
|
codes[i].style.fontFamily = "'Courier New', Courier, monospace"; |
|
|
} |
|
|
}, |
|
|
displayMarkdown(mdString) { |
|
|
let containerID = "ai_input_md_dispalyer"; |
|
|
|
|
|
let container = document.getElementById(containerID); |
|
|
if (container === null) { |
|
|
container = |
|
|
document.getElementById(containerID) || document.createElement("div"); |
|
|
container.id = containerID; |
|
|
document.body.appendChild(container); |
|
|
|
|
|
container.style.zIndex = "100000"; |
|
|
container.style.position = "fixed"; |
|
|
container.style.top = "85vh"; |
|
|
container.style.left = "0"; |
|
|
container.style.height = "40vh"; |
|
|
container.style.width = "80vw"; |
|
|
container.style.backgroundColor = "rgba{20,20,50,1}"; |
|
|
} |
|
|
|
|
|
utils.renderMarkdown(mdString, container); |
|
|
let div = document.createElement('div'); |
|
|
div.style.height = '3000px'; |
|
|
container.appendChild(div) |
|
|
}, |
|
|
moveElementNearMouse: (mElem, targetElement, alwayInWindow = true, event) => { |
|
|
let x = event.clientX + 200; |
|
|
let y = event.clientY - 20; |
|
|
console.log('moveElementNearMouse: ', x, y); |
|
|
if (alwayInWindow) { |
|
|
x = Math.abs(x); |
|
|
y = Math.abs(y); |
|
|
x = Math.min(x, window.innerWidth - mElem.clientWidth); |
|
|
y = Math.min(y, window.innerHeight - 10 - mElem.clientHeight); |
|
|
} |
|
|
mElem.style.left = x + "px"; |
|
|
mElem.style.top = y + "px"; |
|
|
}, |
|
|
addEventListenerForActualClick(element, handler) { |
|
|
let initialX, initialY; |
|
|
let startTime; |
|
|
|
|
|
element.addEventListener("pointerdown", (event) => { |
|
|
initialX = event.clientX; |
|
|
initialY = event.clientY; |
|
|
startTime = Date.now(); |
|
|
}); |
|
|
element.addEventListener("pointerup", (event) => { |
|
|
const deltaX = Math.abs(event.clientX - initialX); |
|
|
const deltaY = Math.abs(event.clientY - initialY); |
|
|
|
|
|
if (deltaX <= 10 && deltaY <= 10 && Date.now() - startTime < 1000) { |
|
|
console.log( |
|
|
"Minimal mouse movement (< 10px in either direction) and short duration click detected." |
|
|
); |
|
|
handler(event); |
|
|
} |
|
|
}); |
|
|
}, |
|
|
sendKeyEvent(element, key, modifiers) { |
|
|
const eventDown = new KeyboardEvent("keydown", { |
|
|
key: key, |
|
|
code: key.toUpperCase(), |
|
|
bubbles: true, |
|
|
cancelable: true, |
|
|
...modifiers, |
|
|
}); |
|
|
const eventUp = new KeyboardEvent("keyup", { |
|
|
key: key, |
|
|
code: key.toUpperCase(), |
|
|
bubbles: true, |
|
|
cancelable: true, |
|
|
...modifiers, |
|
|
}); |
|
|
|
|
|
element.dispatchEvent(eventDown); |
|
|
element.dispatchEvent(eventUp); |
|
|
}, |
|
|
blobToBase64: function blobToBase64(blob) { |
|
|
if (!(blob instanceof Blob)) { |
|
|
throw new TypeError("Parameter must be a Blob object."); |
|
|
} |
|
|
|
|
|
if (!blob.size) { |
|
|
throw new Error("Empty Blob provided."); |
|
|
} |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = () => resolve(reader.result.split(",")[1]); |
|
|
reader.onerror = reject; |
|
|
reader.readAsDataURL(blob); |
|
|
}); |
|
|
}, |
|
|
|
|
|
playAudioBlob: function playAudioBlob(blob, autoPlay = true) { |
|
|
const audio = new Audio(); |
|
|
audio.src = URL.createObjectURL(blob); |
|
|
audio.controls = true; |
|
|
document.body.prepend(audio); |
|
|
|
|
|
if (autoPlay === true) { |
|
|
audio |
|
|
.play() |
|
|
.then(() => { |
|
|
console.log("Audio played successfully!"); |
|
|
}) |
|
|
.catch((error) => { |
|
|
console.error("Error playing audio:", error); |
|
|
}); |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
async AIComplete(userText, option = { |
|
|
url: '/openai/v1/chat/completions', |
|
|
model: 'llama3-8b-8192', |
|
|
max_tokens: 8000, |
|
|
}) { |
|
|
console.log("AIcomplete(): ", userText); |
|
|
if (utils.checkValidString(userText) === false) { |
|
|
return; |
|
|
} |
|
|
let response = await fetch( |
|
|
option.url, |
|
|
{ |
|
|
headers: { |
|
|
accept: "*/*", |
|
|
"content-type": "application/json", |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
messages: [{ |
|
|
"role": "system", |
|
|
"content": "be concise and clear." |
|
|
}, |
|
|
{ role: "user", content: userText }], |
|
|
model: option.model, |
|
|
tools: [], |
|
|
temperature: 0.7, |
|
|
top_p: 0.8, |
|
|
max_tokens: option.max_tokens || 1000000, |
|
|
}), |
|
|
method: "POST", |
|
|
} |
|
|
); |
|
|
response = await response.json(); |
|
|
let responseMessage = response?.choices[0]?.message?.content; |
|
|
|
|
|
console.log("[leptonComplete(text)]", responseMessage); |
|
|
let mdContainer = document.createElement("div"); |
|
|
document.body.appendChild(mdContainer); |
|
|
utils.displayMarkdown(userText + "\n\n" + option.model + '\n' + responseMessage); |
|
|
return response; |
|
|
|
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export { utils }; |