Valen-API / app.mjs
yuhangzang's picture
Use website text, image and video cookbook examples
4f01dd5 verified
Raw History Blame Contribute Delete
9.02 kB
import {buildPayload, previewPayload, pythonExample, requestDecision} from './api.mjs';
import {words, examples, exampleMedia, ratingOptions} from './locales.mjs';
import {readMedia} from './media.mjs';
const $ = id => document.getElementById(id);
let language = new URL(location.href).searchParams.get('lang') === 'zh' ? 'zh' : 'en';
let imageData, attachment, mediaController, mediaRequired = false, controller, resultData, requestPayload, lastError, state = 'ready', statusKey = '';
const t = key => words[language][key] || key;
function renderLanguage() {
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
document.querySelectorAll('[data-i18n]').forEach(el => {el.textContent = t(el.dataset.i18n);});
document.querySelectorAll('[data-placeholder]').forEach(el => {el.placeholder = t(el.dataset.placeholder);});
$('language').textContent = language === 'en' ? '中文' : 'EN';
$('language').ariaLabel = language === 'en' ? '切换到中文' : 'Switch to English';
$('theme').ariaLabel = language === 'en' ? 'Switch color theme' : '切换主题';
configureOptions(); renderState();
if (resultData) renderAnswer(resultData.answer, resultData.data, requestPayload);
}
function configureOptions() {
$('options-field').hidden = $('mode').value === 'predicate';
$('options-label').textContent = t($('mode').value === 'score' ? 'levelsLabel' : 'choicesLabel');
}
function renderState() {
$('state-badge').dataset.state = state;
$('state-badge').textContent = t(state);
$('status').className = state === 'error' ? 'error' : '';
$('status').textContent = lastError ? (words[language].errors[lastError] || `HTTP ${lastError.replace('http_', '')}`) : statusKey ? t(statusKey) : '';
}
function clearResult() {
resultData = null; lastError = null; state = 'ready'; statusKey = '';
$('answer').hidden = true; $('empty').hidden = false;
$('response-json').textContent = ''; $('probabilities').replaceChildren();renderState();
}
function showError(error) {
state = 'error'; lastError = error instanceof TypeError ? 'network' : error.message;
if (!words[language].errors[lastError] && !/^http_\d+$/.test(lastError)) lastError = 'response';
renderState();
}
function currentPayload() {
if(mediaRequired&&!imageData)throw new Error('image');
return buildPayload({model:$('model').value,mode:$('mode').value,question:$('question').value,
context:$('context').value,options:$('options').value,image:imageData});
}
function updatePreview(report = false) {
try {
const payload = currentPayload();
$('python-code').textContent = pythonExample(payload,attachment);
$('request-json').textContent = JSON.stringify(previewPayload(payload),null,2);
$('copy-code').disabled = false;return payload;
} catch (error) {
$('python-code').textContent = t('noCode');$('request-json').textContent = '';$('copy-code').disabled = true;
if (report) showError(error);
}
}
function renderAnswer(answer, data, payload) {
$('empty').hidden = true;$('answer').hidden = false;
const isPredicate = payload.questions[0].type === 'predicate';
$('decision-title').textContent = isPredicate ? t(answer.title) : answer.title;
$('probabilities').replaceChildren();
for (const row of answer.rows) {
const label = isPredicate ? t(row.label) : row.label;
const block = document.createElement('div');block.className = 'prob-row';
const top = document.createElement('div');top.className = 'prob-label';
const name = document.createElement('span');name.textContent = label;
const number = document.createElement('strong');number.textContent = `${(row.probability*100).toFixed(1)}%`;top.append(name,number);
const progress = document.createElement('progress');progress.max=1;progress.value=row.probability;progress.ariaLabel=label;
block.append(top,progress);$('probabilities').append(block);
}
$('score-note').hidden = payload.questions[0].type !== 'score';
$('confidence').textContent = answer.confidence === undefined ? '' : `${t('confidence')}: ${(answer.confidence*100).toFixed(1)}%`;
$('model-id').textContent = data.model_id ? `${t('modelId')}: ${data.model_id}` : '';
$('response-json').textContent = JSON.stringify(data,null,2);
}
function lock(value) {
$('fields').disabled=value;
document.querySelectorAll('[data-example]').forEach(button=>{button.disabled=value;});
$('preview-code').disabled=value;
}
function removeImage() {
mediaController?.abort();mediaController=null;
$('video-preview').pause();$('video-preview').removeAttribute('src');$('video-preview').load();$('video-preview').hidden=true;
if(attachment?.objectUrl)URL.revokeObjectURL(attachment.objectUrl);
attachment=undefined;imageData=undefined;mediaRequired=false;$('image').value='';
$('thumbnail').removeAttribute('src');$('image-preview').hidden=true;$('video-note').hidden=true;clearResult();updatePreview();
}
async function loadMedia(fileOrExample) {
removeImage();mediaRequired=true;
const active=new AbortController();mediaController=active;
lock(true);state='busy';statusKey='reading';renderState();
try {
let file=fileOrExample;
if(!(file instanceof File)) {
const response=await fetch(file.src,{signal:active.signal});if(!response.ok)throw new Error('image');
file=new File([await response.blob()],file.name,{type:file.kind==='video'?'video/mp4':'image/jpeg'});
}
const next=await readMedia(file,active.signal);
if(active.signal.aborted){URL.revokeObjectURL(next.objectUrl);return;}
attachment=next;imageData=next.image;
$('thumbnail').src=imageData;$('image-name').textContent=next.name;$('image-preview').hidden=false;
if(next.kind==='video'){$('video-preview').src=next.video;$('video-preview').poster=imageData;$('video-preview').hidden=false;$('video-note').hidden=false;}
state='ready';statusKey='';renderState();updatePreview();
} catch(error) {
if(active.signal.aborted)return;
$('image').value='';showError(new Error(error.message==='size'?'size':'image'));updatePreview();
} finally {if(mediaController===active){mediaController=null;lock(false);}}
}
$('image').addEventListener('change',()=>{
const file=$('image').files[0];if(!file){removeImage();return;}void loadMedia(file);
});
$('decision-form').addEventListener('submit', async event => {
event.preventDefault();if(controller||mediaController)return;
clearResult();const payload=updatePreview(true);if(!payload)return;
requestPayload=payload;controller=new AbortController();const active=controller;
const timeout=setTimeout(()=>active.abort('timeout'),120000);
lock(true);$('cancel').hidden=false;state='busy';statusKey='waiting';renderState();
try {
const result=await requestDecision(payload,active.signal);if(active.signal.aborted)return;
resultData=result;state='success';statusKey='';renderState();renderAnswer(result.answer,result.data,payload);
} catch(error) {
if(active.signal.aborted) {
if(active.signal.reason==='timeout')showError(new Error('timeout'));
else{state='ready';statusKey='cancelled';renderState();}
} else showError(error);
} finally {clearTimeout(timeout);if(controller===active){controller=null;lock(false);$('cancel').hidden=true;}}
});
$('cancel').addEventListener('click',()=>controller?.abort('cancelled'));
$('remove-image').addEventListener('click',removeImage);
$('language').addEventListener('click',()=>{language=language==='en'?'zh':'en';renderLanguage();updatePreview();});
$('theme').addEventListener('click',()=>{document.body.dataset.theme=document.body.dataset.theme==='dark'?'light':'dark';});
$('mode').addEventListener('change',()=>{
if($('mode').value==='score'&&!$('options').value.trim())$('options').value=ratingOptions[language];configureOptions();
});
for (const name of ['question','context','options','model','mode']) {
$(name).addEventListener('input',()=>{clearResult();updatePreview();});
$(name).addEventListener('change',()=>{configureOptions();updatePreview();});
}
document.querySelectorAll('[data-example]').forEach(button=>button.addEventListener('click',async()=>{
removeImage();const index=Number(button.dataset.example);const example=examples[language][index];
['mode','question','context','options'].forEach((id,index)=>{$(id).value=example[index];});
configureOptions();if(exampleMedia[index])await loadMedia(exampleMedia[index]);else updatePreview();
$('question').focus({preventScroll:true});
$('decision-form').scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth',block:'center'});
}));
$('preview-code').addEventListener('click',()=>{updatePreview(true);});
$('copy-code').addEventListener('click',async()=>{
try{await navigator.clipboard.writeText($('python-code').textContent);$('copy-status').textContent=t('copied');}
catch{$('copy-status').textContent=t('copyFailed');}
});
document.body.dataset.theme=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
renderLanguage();updatePreview();