|
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/[email protected]"; |
|
|
|
|
|
const editor = ace.edit("editor"); |
|
editor.setTheme("ace/theme/monokai"); |
|
editor.session.setMode("ace/mode/xml"); |
|
|
|
|
|
editor.container.addEventListener('contextmenu', showContextMenu); |
|
|
|
|
|
let whisperPipeline; |
|
let mediaRecorder; |
|
let audioChunks = []; |
|
|
|
async function initWhisper() { |
|
|
|
$('#loadingSpinner').show(); |
|
try { |
|
whisperPipeline = await pipeline('automatic-speech-recognition', 'Xenova/whisper-small', |
|
{ |
|
device: "webgpu", |
|
dtype: 'fp32' |
|
},); |
|
|
|
$('#loadingSpinner').hide(); |
|
$('#status').text('Ready to record'); |
|
} catch (e) { |
|
$('#status').text('Error initializing Whisper: ' + e.message); |
|
$('#loadingSpinner').hide(); |
|
} |
|
} |
|
|
|
|
|
async function startRecording() { |
|
try { |
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
|
mediaRecorder = new MediaRecorder(stream); |
|
audioChunks = []; |
|
|
|
mediaRecorder.ondataavailable = (event) => { |
|
audioChunks.push(event.data); |
|
}; |
|
|
|
mediaRecorder.onstop = async () => { |
|
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); |
|
await processAudio(audioBlob); |
|
}; |
|
|
|
mediaRecorder.start(); |
|
$('#startRecording').prop('disabled', true); |
|
$('#stopRecording').prop('disabled', false); |
|
$('#status').text('Recording...'); |
|
} catch (e) { |
|
$('#status').text('Error starting recording: ' + e.message); |
|
} |
|
} |
|
|
|
async function stopRecording() { |
|
if (mediaRecorder && mediaRecorder.state === 'recording') { |
|
mediaRecorder.stop(); |
|
$('#startRecording').prop('disabled', false); |
|
$('#stopRecording').prop('disabled', true); |
|
$('#status').text('Processing audio...'); |
|
} |
|
} |
|
|
|
async function processAudio(audioBlob) { |
|
$('#loadingSpinner').show(); |
|
try { |
|
|
|
const audioUrl = URL.createObjectURL(audioBlob); |
|
|
|
const transcription = await whisperPipeline(audioUrl); |
|
$('#loadingSpinner').hide(); |
|
$('#transcription').val(transcription.text); |
|
|
|
|
|
URL.revokeObjectURL(audioUrl); |
|
} catch (e) { |
|
$('#loadingSpinner').hide(); |
|
$('#status').text('Error processing audio: ' + e.message); |
|
} |
|
} |
|
|
|
|
|
async function sendPrompt() { |
|
$('#loadingSpinner').show(); |
|
const transcription = $('#transcription').val(); |
|
const context = $('#context').text(); |
|
const userPrompt = `${transcription}\n#Context:${context}`; |
|
|
|
|
|
const response = await fetch('http://129.80.86.176:11434/api/generate', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
model: 'hf.co/Geraldine/FineLlama-3.2-3B-Instruct-ead-GGUF:Q5_K_M', |
|
prompt: `Generate EAD/XML for the following archival description: ${userPrompt}`, |
|
stream: false |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
/ Check if context is empty |
|
if (context.trim() === '') { |
|
|
|
editor.setValue(data.response); |
|
} else { |
|
|
|
const selectionRange = editor.getSelectionRange(); |
|
|
|
|
|
editor.session.replace(selectionRange, data.response); |
|
} |
|
$('#status').text('Ready'); |
|
$('#loadingSpinner').hide(); |
|
} |
|
|
|
|
|
function showContextMenu(event) { |
|
event.preventDefault(); |
|
|
|
const selectedText = editor.getSelectedText(); |
|
if (selectedText) { |
|
|
|
let $contextMenu = $('#contextMenu'); |
|
if ($contextMenu.length === 0) { |
|
$contextMenu = $('<div>', { |
|
id: 'contextMenu', |
|
css: { |
|
position: 'absolute', |
|
backgroundColor: 'white', |
|
border: '1px solid #ccc', |
|
zIndex: 1000, |
|
display: 'none' |
|
} |
|
}).append('<button id="addToContext">Add to Context</button>').appendTo('body'); |
|
} |
|
|
|
|
|
$contextMenu.css({ |
|
left: `${event.pageX}px`, |
|
top: `${event.pageY}px`, |
|
display: 'block' |
|
}); |
|
|
|
|
|
$('#addToContext').off('click').on('click', () => { |
|
addToContext(selectedText); |
|
$contextMenu.hide(); |
|
}); |
|
} |
|
} |
|
|
|
|
|
function addToContext(selectedText) { |
|
$('#context').text(selectedText); |
|
} |
|
|
|
function formatXmlInEditor() { |
|
const xmlContent = editor.getValue(); |
|
const formattedXml = formatXml(xmlContent); |
|
editor.setValue(formattedXml, 1); |
|
} |
|
|
|
function formatXml(xml, tab) { |
|
let formatted = ''; |
|
let indentLevel = 0; |
|
tab = tab || ' '; |
|
|
|
|
|
xml = xml.replace(/>\s*</g, '><').trim(); |
|
|
|
|
|
xml.split(/(<[^>]+>)/g).forEach(node => { |
|
if (node.trim()) { |
|
if (node.startsWith('</')) { |
|
|
|
indentLevel--; |
|
formatted += `${tab.repeat(indentLevel)}${node.trim()}\n`; |
|
} else if (node.startsWith('<') && !node.endsWith('/>')) { |
|
|
|
formatted += `${tab.repeat(indentLevel)}${node.trim()}\n`; |
|
indentLevel++; |
|
} else if (node.startsWith('<') && node.endsWith('/>')) { |
|
|
|
formatted += `${tab.repeat(indentLevel)}${node.trim()}\n`; |
|
} else { |
|
|
|
formatted += `${tab.repeat(indentLevel)}${node.trim()}\n`; |
|
} |
|
} |
|
}); |
|
|
|
return formatted.trim(); |
|
} |
|
|
|
|
|
|
|
|
|
$('#startRecording').on('click', startRecording); |
|
$('#stopRecording').on('click', stopRecording); |
|
|
|
|
|
$(document).on('click', () => { |
|
$('#contextMenu').hide(); |
|
}); |
|
|
|
|
|
$('#sendPrompt').on('click', sendPrompt); |
|
|
|
|
|
$('#prettifyXML').on('click', formatXmlInEditor); |
|
|
|
|
|
initWhisper(); |