| import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0"; |
|
|
| |
| 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(); |
| |
| 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(); |