Skip to content

Commit 532a4d4

Browse files
Eranclaude
andcommitted
v0.0.9 - file upload support for Media and Audio tabs
- Media tab: "Choose file from computer" option alongside URL input - Audio tab: same, with audio/* file filter - Files read as base64 via FileReader and sent directly to Evolution API - Auto-fills MIME type and filename from selected file - Green badge shows selected filename; cleared if URL is typed instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ab560d9 commit 532a4d4

File tree

1 file changed

+62
-6
lines changed

1 file changed

+62
-6
lines changed

index.html

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,16 @@ <h2 class="text-lg font-semibold text-gray-800 mb-1">Send Media</h2>
278278
</div>
279279
<div>
280280
<label class="block text-sm font-medium text-gray-700 mb-1.5">Media URL <span class="text-red-400">*</span></label>
281-
<input id="media-url" type="text" placeholder="https://example.com/image.jpg" class="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm"/>
281+
<input id="media-url" type="text" placeholder="https://example.com/image.jpg" class="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm" oninput="mediaFileBase64=null;document.getElementById('media-file-badge').classList.add('hidden')"/>
282+
<div class="flex items-center gap-2 mt-2">
283+
<span class="text-xs text-gray-400">or</span>
284+
<label class="cursor-pointer inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 font-medium">
285+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
286+
Choose file from computer
287+
<input type="file" id="media-file" class="hidden" onchange="onMediaFileSelected(this)"/>
288+
</label>
289+
<span id="media-file-badge" class="hidden text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full"></span>
290+
</div>
282291
</div>
283292
<div>
284293
<label class="block text-sm font-medium text-gray-700 mb-1.5">MIME Type <span class="text-red-400">*</span></label>
@@ -310,7 +319,16 @@ <h2 class="text-lg font-semibold text-gray-800 mb-1">Send Voice Note</h2>
310319
</div>
311320
<div>
312321
<label class="block text-sm font-medium text-gray-700 mb-1.5">Audio URL <span class="text-red-400">*</span></label>
313-
<input id="audio-url" type="text" placeholder="https://example.com/audio.mp3" class="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm"/>
322+
<input id="audio-url" type="text" placeholder="https://example.com/audio.mp3" class="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm" oninput="audioFileBase64=null;document.getElementById('audio-file-badge').classList.add('hidden')"/>
323+
<div class="flex items-center gap-2 mt-2">
324+
<span class="text-xs text-gray-400">or</span>
325+
<label class="cursor-pointer inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 font-medium">
326+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
327+
Choose file from computer
328+
<input type="file" id="audio-file" class="hidden" accept="audio/*" onchange="onAudioFileSelected(this)"/>
329+
</label>
330+
<span id="audio-file-badge" class="hidden text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full"></span>
331+
</div>
314332
<p class="text-xs text-gray-400 mt-1">Supported: mp3, ogg, m4a, wav</p>
315333
</div>
316334
<div class="flex items-center gap-2">
@@ -1808,6 +1826,43 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18081826
sendBtn.disabled = false;
18091827
}
18101828

1829+
// ── FILE → BASE64 HELPERS ─────────────────────────────────────────────────
1830+
let mediaFileBase64 = null;
1831+
let audioFileBase64 = null;
1832+
1833+
function readFileAsBase64(file) {
1834+
return new Promise((resolve, reject) => {
1835+
const reader = new FileReader();
1836+
reader.onload = e => resolve(e.target.result.split(',')[1]);
1837+
reader.onerror = reject;
1838+
reader.readAsDataURL(file);
1839+
});
1840+
}
1841+
1842+
async function onMediaFileSelected(input) {
1843+
const file = input.files[0];
1844+
if (!file) return;
1845+
mediaFileBase64 = await readFileAsBase64(file);
1846+
document.getElementById('media-url').value = '';
1847+
document.getElementById('media-url').placeholder = file.name;
1848+
if (file.type) document.getElementById('media-mimetype').value = file.type;
1849+
if (!document.getElementById('media-filename').value) document.getElementById('media-filename').value = file.name;
1850+
const badge = document.getElementById('media-file-badge');
1851+
badge.textContent = file.name;
1852+
badge.classList.remove('hidden');
1853+
}
1854+
1855+
async function onAudioFileSelected(input) {
1856+
const file = input.files[0];
1857+
if (!file) return;
1858+
audioFileBase64 = await readFileAsBase64(file);
1859+
document.getElementById('audio-url').value = '';
1860+
document.getElementById('audio-url').placeholder = file.name;
1861+
const badge = document.getElementById('audio-file-badge');
1862+
badge.textContent = file.name;
1863+
badge.classList.remove('hidden');
1864+
}
1865+
18111866
// ── SEND MEDIA ────────────────────────────────────────────────────────────
18121867
function updateMimetype() {
18131868
const type = document.getElementById('media-type').value;
@@ -1820,11 +1875,12 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18201875
const number = validatePhone(rawNum);
18211876
if (!number) return showFieldError(document.getElementById('media-number'), rawNum ? `"${rawNum}" is not a valid number` : 'Number is required');
18221877
const mediatype = document.getElementById('media-type').value;
1823-
const media = document.getElementById('media-url').value.trim();
1878+
const media = mediaFileBase64 || document.getElementById('media-url').value.trim();
18241879
const mimetype = document.getElementById('media-mimetype').value.trim();
18251880
const fileName = document.getElementById('media-filename').value.trim();
18261881
const caption = document.getElementById('media-caption').value.trim();
1827-
if (!media || !mimetype) return alert('URL and MIME type are required.');
1882+
if (!media) return alert('Provide a URL or choose a file.');
1883+
if (!mimetype) return alert('MIME type is required.');
18281884
const body = { number, mediatype, media, mimetype };
18291885
if (fileName) body.fileName = fileName;
18301886
if (caption) body.caption = caption;
@@ -1836,9 +1892,9 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18361892
const rawNum = document.getElementById('audio-number').value.trim();
18371893
const number = validatePhone(rawNum);
18381894
if (!number) return showFieldError(document.getElementById('audio-number'), rawNum ? `"${rawNum}" is not a valid number` : 'Number is required');
1839-
const audio = document.getElementById('audio-url').value.trim();
1895+
const audio = audioFileBase64 || document.getElementById('audio-url').value.trim();
18401896
const encoding = document.getElementById('audio-encoding').checked;
1841-
if (!audio) return alert('Audio URL is required.');
1897+
if (!audio) return alert('Provide a URL or choose an audio file.');
18421898
await apiCall(`/message/sendWhatsAppAudio/${getCfg().instance}`, { number, audio, encoding });
18431899
}
18441900

0 commit comments

Comments
 (0)