Spaces:
Running
Running
Use website text, image and video cookbook examples
Browse files- .gitattributes +1 -0
- NOTICE.md +9 -0
- README.md +13 -3
- api.mjs +27 -5
- app.mjs +36 -27
- cookbook/momos-frame.jpg +0 -0
- cookbook/momos.mp4 +3 -0
- cookbook/sheep.jpg +0 -0
- index.html +4 -4
- locales.mjs +19 -10
- media.mjs +42 -0
- style.css +2 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
assets/valen-mark.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
assets/valen-mark.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
cookbook/momos.mp4 filter=lfs diff=lfs merge=lfs -text
|
NOTICE.md
CHANGED
|
@@ -10,3 +10,12 @@ This Space uses a subset of the Valen shared design system, version 0.5.1.
|
|
| 10 |
Valen branding identifies Valen Team and its projects. No trademark rights are
|
| 11 |
granted. No additional open-source license is asserted for the application or
|
| 12 |
design source. Snapshot hashes are recorded in `assets/manifest.json`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
Valen branding identifies Valen Team and its projects. No trademark rights are
|
| 11 |
granted. No additional open-source license is asserted for the application or
|
| 12 |
design source. Snapshot hashes are recorded in `assets/manifest.json`.
|
| 13 |
+
|
| 14 |
+
## Cookbook examples
|
| 15 |
+
|
| 16 |
+
The text, image and video examples are adapted from the Valen website's
|
| 17 |
+
`src/components/modern/cookbook.ts` and the official
|
| 18 |
+
[API cookbook](https://github.com/Valen-Team/Valen/blob/main/cookbook/api_quickstart.ipynb).
|
| 19 |
+
`cookbook/sheep.jpg`, `cookbook/momos.mp4` and `cookbook/momos-frame.jpg` are copied
|
| 20 |
+
unchanged from `valen-team.github.io/static/cookbook/`. They retain their upstream
|
| 21 |
+
rights and attribution. Video preprocessing is adapted from the website's `media.ts`.
|
README.md
CHANGED
|
@@ -6,13 +6,13 @@ colorTo: gray
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
fullWidth: true
|
| 9 |
-
short_description: Valen
|
| 10 |
---
|
| 11 |
|
| 12 |
# Valen API
|
| 13 |
|
| 14 |
A bilingual, static playground for the [Valen Decisions API](https://valen-team.github.io/docs/valen/api/).
|
| 15 |
-
Use text or
|
| 16 |
Results show probability bars, optional confidence, the returned model ID, and raw JSON.
|
| 17 |
The Python example follows the current inputs, so you can reproduce a request in your own application.
|
| 18 |
|
|
@@ -65,7 +65,16 @@ browser flow on the deployed Space. `?lang=zh` opens the Chinese interface.
|
|
| 65 |
Questions are limited to 1000 characters, context to 4000, each option to 300.
|
| 66 |
JPG/PNG/WebP uploads are limited to 3 MB. Images are resized locally to fit
|
| 67 |
1536 × 1536 and encoded as JPEG before submission. The request preview omits the
|
| 68 |
-
base64 bytes; the generated Python example reads
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
## Data
|
| 71 |
|
|
@@ -79,6 +88,7 @@ Requests can be cancelled and time out after two minutes.
|
|
| 79 |
|
| 80 |
- `index.html`, `style.css`, `app.mjs`, `locales.mjs`: responsive bilingual UI.
|
| 81 |
- `api.mjs`: request validation, response parsing and Python examples.
|
|
|
|
| 82 |
- `tests/`: offline contract, error and example-generation tests.
|
| 83 |
- `assets/`: pinned Valen design tokens, brand mark and font; see `NOTICE.md`.
|
| 84 |
|
|
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
fullWidth: true
|
| 9 |
+
short_description: Valen API examples with text, images and video
|
| 10 |
---
|
| 11 |
|
| 12 |
# Valen API
|
| 13 |
|
| 14 |
A bilingual, static playground for the [Valen Decisions API](https://valen-team.github.io/docs/valen/api/).
|
| 15 |
+
Use text, images, or a video frame with native **predicate**, **choice**, and **score** questions.
|
| 16 |
Results show probability bars, optional confidence, the returned model ID, and raw JSON.
|
| 17 |
The Python example follows the current inputs, so you can reproduce a request in your own application.
|
| 18 |
|
|
|
|
| 65 |
Questions are limited to 1000 characters, context to 4000, each option to 300.
|
| 66 |
JPG/PNG/WebP uploads are limited to 3 MB. Images are resized locally to fit
|
| 67 |
1536 × 1536 and encoded as JPEG before submission. The request preview omits the
|
| 68 |
+
base64 bytes; the generated Python example reads the selected local filename.
|
| 69 |
+
MP4/WebM videos up to 20 MB are decoded in the browser, and only a JPEG of the
|
| 70 |
+
middle frame is sent as `input_image`. This is not whole-video inference; a single
|
| 71 |
+
frame cannot establish motion or event order. The Python video example uses PyAV
|
| 72 |
+
to extract a middle frame locally.
|
| 73 |
+
|
| 74 |
+
The three examples match the website cookbook: a customer changing a delivery
|
| 75 |
+
address (text), naming a group of sheep (image), and identifying the sauce color
|
| 76 |
+
in `momos.mp4` (video). Example media is bundled in `cookbook/`. Clicking an
|
| 77 |
+
example loads its inputs and prepares the image/frame without calling the API.
|
| 78 |
|
| 79 |
## Data
|
| 80 |
|
|
|
|
| 88 |
|
| 89 |
- `index.html`, `style.css`, `app.mjs`, `locales.mjs`: responsive bilingual UI.
|
| 90 |
- `api.mjs`: request validation, response parsing and Python examples.
|
| 91 |
+
- `media.mjs`: local image/video decoding and frame extraction.
|
| 92 |
- `tests/`: offline contract, error and example-generation tests.
|
| 93 |
- `assets/`: pinned Valen design tokens, brand mark and font; see `NOTICE.md`.
|
| 94 |
|
api.mjs
CHANGED
|
@@ -27,16 +27,38 @@ export function previewPayload(payload) {
|
|
| 27 |
return value;
|
| 28 |
}
|
| 29 |
|
| 30 |
-
export function pythonExample(payload) {
|
| 31 |
const value = structuredClone(payload);
|
| 32 |
const hasImage = Array.isArray(value.input);
|
| 33 |
if (hasImage) value.input[0].content[1].image_url = 'IMAGE_DATA_URL';
|
| 34 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
import io
|
| 36 |
from PIL import Image, ImageOps
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
with Image.open(
|
| 40 |
image = ImageOps.exif_transpose(image).convert("RGB")
|
| 41 |
image.thumbnail((1536, 1536))
|
| 42 |
buffer = io.BytesIO()
|
|
@@ -46,7 +68,7 @@ image_url = "data:image/jpeg;base64," + base64.b64encode(buffer.getvalue()).deco
|
|
| 46 |
` : '';
|
| 47 |
// json.loads safely preserves quotes, Unicode and literal user text in Python.
|
| 48 |
const literal = JSON.stringify(JSON.stringify(value)).replaceAll('\u2028', '\\u2028').replaceAll('\u2029', '\\u2029');
|
| 49 |
-
return `# pip install requests${hasImage ? ' Pillow' : ''}
|
| 50 |
import json
|
| 51 |
import requests
|
| 52 |
${setup}
|
|
|
|
| 27 |
return value;
|
| 28 |
}
|
| 29 |
|
| 30 |
+
export function pythonExample(payload, attachment) {
|
| 31 |
const value = structuredClone(payload);
|
| 32 |
const hasImage = Array.isArray(value.input);
|
| 33 |
if (hasImage) value.input[0].content[1].image_url = 'IMAGE_DATA_URL';
|
| 34 |
+
const isVideo=hasImage&&attachment?.kind==='video';
|
| 35 |
+
const filename=JSON.stringify(attachment?.name||(isVideo?'video.mp4':'image.jpg'));
|
| 36 |
+
const setup = isVideo ? `import base64
|
| 37 |
+
import io
|
| 38 |
+
import av
|
| 39 |
+
|
| 40 |
+
# Extract the middle frame, as in the Valen cookbook.
|
| 41 |
+
video_path = ${filename}
|
| 42 |
+
with av.open(video_path) as video:
|
| 43 |
+
frame_count = sum(1 for _ in video.decode(video=0))
|
| 44 |
+
if frame_count == 0:
|
| 45 |
+
raise ValueError("The video has no decodable frames")
|
| 46 |
+
with av.open(video_path) as video:
|
| 47 |
+
for index, frame in enumerate(video.decode(video=0)):
|
| 48 |
+
if index == frame_count // 2:
|
| 49 |
+
image = frame.to_image().convert("RGB")
|
| 50 |
+
break
|
| 51 |
+
image.thumbnail((1536, 1536))
|
| 52 |
+
buffer = io.BytesIO()
|
| 53 |
+
image.save(buffer, format="JPEG", quality=85)
|
| 54 |
+
image_url = "data:image/jpeg;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
|
| 55 |
+
|
| 56 |
+
` : hasImage ? `import base64
|
| 57 |
import io
|
| 58 |
from PIL import Image, ImageOps
|
| 59 |
|
| 60 |
+
# Use your selected image in the current directory.
|
| 61 |
+
with Image.open(${filename}) as image:
|
| 62 |
image = ImageOps.exif_transpose(image).convert("RGB")
|
| 63 |
image.thumbnail((1536, 1536))
|
| 64 |
buffer = io.BytesIO()
|
|
|
|
| 68 |
` : '';
|
| 69 |
// json.loads safely preserves quotes, Unicode and literal user text in Python.
|
| 70 |
const literal = JSON.stringify(JSON.stringify(value)).replaceAll('\u2028', '\\u2028').replaceAll('\u2029', '\\u2029');
|
| 71 |
+
return `# pip install requests${hasImage ? ' Pillow' : ''}${isVideo ? ' av==16.1.0' : ''}
|
| 72 |
import json
|
| 73 |
import requests
|
| 74 |
${setup}
|
app.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
import {buildPayload, previewPayload, pythonExample, requestDecision} from './api.mjs';
|
| 2 |
-
import {words, examples} from './locales.mjs';
|
|
|
|
| 3 |
const $ = id => document.getElementById(id);
|
| 4 |
let language = new URL(location.href).searchParams.get('lang') === 'zh' ? 'zh' : 'en';
|
| 5 |
-
let imageData,
|
| 6 |
const t = key => words[language][key] || key;
|
| 7 |
|
| 8 |
function renderLanguage() {
|
|
@@ -36,13 +37,14 @@ function showError(error) {
|
|
| 36 |
renderState();
|
| 37 |
}
|
| 38 |
function currentPayload() {
|
|
|
|
| 39 |
return buildPayload({model:$('model').value,mode:$('mode').value,question:$('question').value,
|
| 40 |
context:$('context').value,options:$('options').value,image:imageData});
|
| 41 |
}
|
| 42 |
function updatePreview(report = false) {
|
| 43 |
try {
|
| 44 |
const payload = currentPayload();
|
| 45 |
-
$('python-code').textContent = pythonExample(payload);
|
| 46 |
$('request-json').textContent = JSON.stringify(previewPayload(payload),null,2);
|
| 47 |
$('copy-code').disabled = false;return payload;
|
| 48 |
} catch (error) {
|
|
@@ -75,32 +77,38 @@ function lock(value) {
|
|
| 75 |
$('preview-code').disabled=value;
|
| 76 |
}
|
| 77 |
function removeImage() {
|
| 78 |
-
|
| 79 |
-
$('
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
const
|
| 84 |
-
lock(true);state='busy';statusKey='reading';renderState();
|
| 85 |
try {
|
| 86 |
-
|
| 87 |
-
if
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
const
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
imageData=
|
| 95 |
-
$('
|
| 96 |
state='ready';statusKey='';renderState();updatePreview();
|
| 97 |
} catch(error) {
|
| 98 |
-
if(
|
| 99 |
-
$('image').value='';showError(new Error(error.message==='size'?'size':'image'));
|
| 100 |
-
} finally {if(
|
|
|
|
|
|
|
|
|
|
| 101 |
});
|
| 102 |
$('decision-form').addEventListener('submit', async event => {
|
| 103 |
-
event.preventDefault();if(controller)return;
|
| 104 |
clearResult();const payload=updatePreview(true);if(!payload)return;
|
| 105 |
requestPayload=payload;controller=new AbortController();const active=controller;
|
| 106 |
const timeout=setTimeout(()=>active.abort('timeout'),120000);
|
|
@@ -120,16 +128,17 @@ $('remove-image').addEventListener('click',removeImage);
|
|
| 120 |
$('language').addEventListener('click',()=>{language=language==='en'?'zh':'en';renderLanguage();updatePreview();});
|
| 121 |
$('theme').addEventListener('click',()=>{document.body.dataset.theme=document.body.dataset.theme==='dark'?'light':'dark';});
|
| 122 |
$('mode').addEventListener('change',()=>{
|
| 123 |
-
if($('mode').value==='score'&&!$('options').value.trim())$('options').value=
|
| 124 |
});
|
| 125 |
for (const name of ['question','context','options','model','mode']) {
|
| 126 |
$(name).addEventListener('input',()=>{clearResult();updatePreview();});
|
| 127 |
$(name).addEventListener('change',()=>{configureOptions();updatePreview();});
|
| 128 |
}
|
| 129 |
-
document.querySelectorAll('[data-example]').forEach(button=>button.addEventListener('click',()=>{
|
| 130 |
-
removeImage();const
|
| 131 |
['mode','question','context','options'].forEach((id,index)=>{$(id).value=example[index];});
|
| 132 |
-
configureOptions();
|
|
|
|
| 133 |
$('decision-form').scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth',block:'center'});
|
| 134 |
}));
|
| 135 |
$('preview-code').addEventListener('click',()=>{updatePreview(true);});
|
|
|
|
| 1 |
import {buildPayload, previewPayload, pythonExample, requestDecision} from './api.mjs';
|
| 2 |
+
import {words, examples, exampleMedia, ratingOptions} from './locales.mjs';
|
| 3 |
+
import {readMedia} from './media.mjs';
|
| 4 |
const $ = id => document.getElementById(id);
|
| 5 |
let language = new URL(location.href).searchParams.get('lang') === 'zh' ? 'zh' : 'en';
|
| 6 |
+
let imageData, attachment, mediaController, mediaRequired = false, controller, resultData, requestPayload, lastError, state = 'ready', statusKey = '';
|
| 7 |
const t = key => words[language][key] || key;
|
| 8 |
|
| 9 |
function renderLanguage() {
|
|
|
|
| 37 |
renderState();
|
| 38 |
}
|
| 39 |
function currentPayload() {
|
| 40 |
+
if(mediaRequired&&!imageData)throw new Error('image');
|
| 41 |
return buildPayload({model:$('model').value,mode:$('mode').value,question:$('question').value,
|
| 42 |
context:$('context').value,options:$('options').value,image:imageData});
|
| 43 |
}
|
| 44 |
function updatePreview(report = false) {
|
| 45 |
try {
|
| 46 |
const payload = currentPayload();
|
| 47 |
+
$('python-code').textContent = pythonExample(payload,attachment);
|
| 48 |
$('request-json').textContent = JSON.stringify(previewPayload(payload),null,2);
|
| 49 |
$('copy-code').disabled = false;return payload;
|
| 50 |
} catch (error) {
|
|
|
|
| 77 |
$('preview-code').disabled=value;
|
| 78 |
}
|
| 79 |
function removeImage() {
|
| 80 |
+
mediaController?.abort();mediaController=null;
|
| 81 |
+
$('video-preview').pause();$('video-preview').removeAttribute('src');$('video-preview').load();$('video-preview').hidden=true;
|
| 82 |
+
if(attachment?.objectUrl)URL.revokeObjectURL(attachment.objectUrl);
|
| 83 |
+
attachment=undefined;imageData=undefined;mediaRequired=false;$('image').value='';
|
| 84 |
+
$('thumbnail').removeAttribute('src');$('image-preview').hidden=true;$('video-note').hidden=true;clearResult();updatePreview();
|
| 85 |
}
|
| 86 |
+
async function loadMedia(fileOrExample) {
|
| 87 |
+
removeImage();mediaRequired=true;
|
| 88 |
+
const active=new AbortController();mediaController=active;
|
| 89 |
+
lock(true);state='busy';statusKey='reading';renderState();
|
| 90 |
try {
|
| 91 |
+
let file=fileOrExample;
|
| 92 |
+
if(!(file instanceof File)) {
|
| 93 |
+
const response=await fetch(file.src,{signal:active.signal});if(!response.ok)throw new Error('image');
|
| 94 |
+
file=new File([await response.blob()],file.name,{type:file.kind==='video'?'video/mp4':'image/jpeg'});
|
| 95 |
+
}
|
| 96 |
+
const next=await readMedia(file,active.signal);
|
| 97 |
+
if(active.signal.aborted){URL.revokeObjectURL(next.objectUrl);return;}
|
| 98 |
+
attachment=next;imageData=next.image;
|
| 99 |
+
$('thumbnail').src=imageData;$('image-name').textContent=next.name;$('image-preview').hidden=false;
|
| 100 |
+
if(next.kind==='video'){$('video-preview').src=next.video;$('video-preview').poster=imageData;$('video-preview').hidden=false;$('video-note').hidden=false;}
|
| 101 |
state='ready';statusKey='';renderState();updatePreview();
|
| 102 |
} catch(error) {
|
| 103 |
+
if(active.signal.aborted)return;
|
| 104 |
+
$('image').value='';showError(new Error(error.message==='size'?'size':'image'));updatePreview();
|
| 105 |
+
} finally {if(mediaController===active){mediaController=null;lock(false);}}
|
| 106 |
+
}
|
| 107 |
+
$('image').addEventListener('change',()=>{
|
| 108 |
+
const file=$('image').files[0];if(!file){removeImage();return;}void loadMedia(file);
|
| 109 |
});
|
| 110 |
$('decision-form').addEventListener('submit', async event => {
|
| 111 |
+
event.preventDefault();if(controller||mediaController)return;
|
| 112 |
clearResult();const payload=updatePreview(true);if(!payload)return;
|
| 113 |
requestPayload=payload;controller=new AbortController();const active=controller;
|
| 114 |
const timeout=setTimeout(()=>active.abort('timeout'),120000);
|
|
|
|
| 128 |
$('language').addEventListener('click',()=>{language=language==='en'?'zh':'en';renderLanguage();updatePreview();});
|
| 129 |
$('theme').addEventListener('click',()=>{document.body.dataset.theme=document.body.dataset.theme==='dark'?'light':'dark';});
|
| 130 |
$('mode').addEventListener('change',()=>{
|
| 131 |
+
if($('mode').value==='score'&&!$('options').value.trim())$('options').value=ratingOptions[language];configureOptions();
|
| 132 |
});
|
| 133 |
for (const name of ['question','context','options','model','mode']) {
|
| 134 |
$(name).addEventListener('input',()=>{clearResult();updatePreview();});
|
| 135 |
$(name).addEventListener('change',()=>{configureOptions();updatePreview();});
|
| 136 |
}
|
| 137 |
+
document.querySelectorAll('[data-example]').forEach(button=>button.addEventListener('click',async()=>{
|
| 138 |
+
removeImage();const index=Number(button.dataset.example);const example=examples[language][index];
|
| 139 |
['mode','question','context','options'].forEach((id,index)=>{$(id).value=example[index];});
|
| 140 |
+
configureOptions();if(exampleMedia[index])await loadMedia(exampleMedia[index]);else updatePreview();
|
| 141 |
+
$('question').focus({preventScroll:true});
|
| 142 |
$('decision-form').scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth',block:'center'});
|
| 143 |
}));
|
| 144 |
$('preview-code').addEventListener('click',()=>{updatePreview(true);});
|
cookbook/momos-frame.jpg
ADDED
|
cookbook/momos.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1b51c243276e7016ddce3643891400570a079660ccf02b3bac7afe94827e2e6a
|
| 3 |
+
size 395352
|
cookbook/sheep.jpg
ADDED
|
index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
-
<meta name="description" content="Try Valen's free decision API with text and
|
| 7 |
<meta name="color-scheme" content="light dark">
|
| 8 |
<title>Valen API</title>
|
| 9 |
<link rel="icon" href="./assets/valen-mark.png">
|
|
@@ -13,7 +13,7 @@
|
|
| 13 |
<body>
|
| 14 |
<header class="nav"><a class="brand" href="https://valen-team.github.io/" target="_blank" rel="noreferrer"><img src="./assets/valen-mark.png" alt=""><strong>valen</strong><span>API</span></a><nav aria-label="Navigation"><a href="https://valen-team.github.io/docs/valen/api/" target="_blank" rel="noreferrer" data-i18n="docs">Documentation ↗</a><button id="language" type="button" aria-label="Switch language">中文</button><button id="theme" type="button" aria-label="Switch color theme">◐</button></nav></header>
|
| 15 |
<main>
|
| 16 |
-
<section class="intro"><span class="eyebrow" data-i18n="eyebrow">THE VALEN API PLAYGROUND</span><h1 data-i18n="title">A question. A clear decision.</h1><p data-i18n="subtitle">Bring text
|
| 17 |
<div class="workspace">
|
| 18 |
<section class="panel input-panel"><div class="panel-heading"><span class="step">01</span><h2 data-i18n="input">Your input</h2></div>
|
| 19 |
<form id="decision-form"><fieldset id="fields">
|
|
@@ -21,7 +21,7 @@
|
|
| 21 |
<label><span data-i18n="question">Question</span><textarea id="question" maxlength="1000" rows="2" required data-placeholder="questionPlaceholder" placeholder="What would you like Valen to decide?"></textarea></label>
|
| 22 |
<label><span data-i18n="context">Context <small>optional</small></span><textarea id="context" maxlength="4000" rows="3" data-placeholder="contextPlaceholder" placeholder="Paste the text to assess, or add some context…"></textarea></label>
|
| 23 |
<label id="options-field" hidden><span id="options-label">Choices · one per line</span><textarea id="options" rows="4" placeholder="Option A Option B"></textarea><small data-i18n="optionsHint">2–8 entries, up to 300 characters each.</small></label>
|
| 24 |
-
<div class="attachment"><label class="upload-label" for="image"><span class="upload-icon">+</span><span><strong data-i18n="attach">Add an image</strong><small data-i18n="imageHint">
|
| 25 |
<button id="submit" class="primary" type="submit"><span data-i18n="submit">Ask Valen</span><span aria-hidden="true">↗</span></button>
|
| 26 |
</fieldset></form>
|
| 27 |
<button id="cancel" class="cancel" type="button" hidden data-i18n="cancel">Cancel request</button>
|
|
@@ -34,7 +34,7 @@
|
|
| 34 |
<div class="api-note"><span class="mini-label">POST</span><code>/v1/decisions</code><p data-i18n="quota">Free capacity is limited. On HTTP 429, wait at least one minute before retrying.</p></div>
|
| 35 |
</section>
|
| 36 |
</div>
|
| 37 |
-
<section class="examples"><div class="section-heading"><h2 data-i18n="examplesTitle">Start with a question</h2><span data-i18n="examplesNote">Loads an example. You choose when to send.</span></div><div class="example-grid"><button type="button" data-example="0"><span data-i18n="example0Tag">
|
| 38 |
<section class="code-section"><details id="code-details"><summary><span data-i18n="codeTitle">Make it your own · API example</span><span class="code-tag">PYTHON</span></summary><p data-i18n="codeNote">Code follows your current input. Generating a preview does not call the API.</p><div class="code-actions"><button id="preview-code" type="button" data-i18n="preview">Update preview</button><button id="copy-code" type="button" data-i18n="copy">Copy Python</button><span id="copy-status" role="status"></span></div><pre><code id="python-code"></code></pre><details><summary data-i18n="requestPreview">Request JSON · image data omitted</summary><pre id="request-json"></pre></details></details></section>
|
| 39 |
<footer><span>© 2026 Valen Team / 万澜</span><p data-i18n="network">Your browser connects directly to api.valen-team.workers.dev. This Space uses the same API as the website.</p><a href="https://huggingface.co/spaces/Valen-Team/Valen-API/tree/main" target="_blank" rel="noreferrer" data-i18n="source">View source ↗</a></footer>
|
| 40 |
</main>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<meta name="description" content="Try Valen's free decision API with text, images and videos. Yes/no, choices, and ratings with ready-to-run Python examples.">
|
| 7 |
<meta name="color-scheme" content="light dark">
|
| 8 |
<title>Valen API</title>
|
| 9 |
<link rel="icon" href="./assets/valen-mark.png">
|
|
|
|
| 13 |
<body>
|
| 14 |
<header class="nav"><a class="brand" href="https://valen-team.github.io/" target="_blank" rel="noreferrer"><img src="./assets/valen-mark.png" alt=""><strong>valen</strong><span>API</span></a><nav aria-label="Navigation"><a href="https://valen-team.github.io/docs/valen/api/" target="_blank" rel="noreferrer" data-i18n="docs">Documentation ↗</a><button id="language" type="button" aria-label="Switch language">中文</button><button id="theme" type="button" aria-label="Switch color theme">◐</button></nav></header>
|
| 15 |
<main>
|
| 16 |
+
<section class="intro"><span class="eyebrow" data-i18n="eyebrow">THE VALEN API PLAYGROUND</span><h1 data-i18n="title">A question. A clear decision.</h1><p data-i18n="subtitle">Bring text, an image, or a video. Get probabilities for what comes next.</p><div class="intro-meta"><span class="dot"></span><span data-i18n="free">Free API · No key needed</span><span class="divider">/</span><span data-i18n="formats">Yes / No · Choices · Rating</span></div></section>
|
| 17 |
<div class="workspace">
|
| 18 |
<section class="panel input-panel"><div class="panel-heading"><span class="step">01</span><h2 data-i18n="input">Your input</h2></div>
|
| 19 |
<form id="decision-form"><fieldset id="fields">
|
|
|
|
| 21 |
<label><span data-i18n="question">Question</span><textarea id="question" maxlength="1000" rows="2" required data-placeholder="questionPlaceholder" placeholder="What would you like Valen to decide?"></textarea></label>
|
| 22 |
<label><span data-i18n="context">Context <small>optional</small></span><textarea id="context" maxlength="4000" rows="3" data-placeholder="contextPlaceholder" placeholder="Paste the text to assess, or add some context…"></textarea></label>
|
| 23 |
<label id="options-field" hidden><span id="options-label">Choices · one per line</span><textarea id="options" rows="4" placeholder="Option A Option B"></textarea><small data-i18n="optionsHint">2–8 entries, up to 300 characters each.</small></label>
|
| 24 |
+
<div class="attachment"><label class="upload-label" for="image"><span class="upload-icon">+</span><span><strong data-i18n="attach">Add an image or video</strong><small data-i18n="imageHint">Images up to 3 MB · MP4 / WebM up to 20 MB</small></span></label><input id="image" type="file" accept="image/jpeg,image/png,image/webp,video/mp4,video/webm"><div id="image-preview" hidden><img id="thumbnail" alt="Selected image"><span id="image-name"></span><button id="remove-image" type="button" data-i18n="remove">Remove</button></div><video id="video-preview" controls playsinline preload="metadata" hidden></video><p id="video-note" class="note" data-i18n="videoNote" hidden>Only the middle frame is sent to the model.</p></div>
|
| 25 |
<button id="submit" class="primary" type="submit"><span data-i18n="submit">Ask Valen</span><span aria-hidden="true">↗</span></button>
|
| 26 |
</fieldset></form>
|
| 27 |
<button id="cancel" class="cancel" type="button" hidden data-i18n="cancel">Cancel request</button>
|
|
|
|
| 34 |
<div class="api-note"><span class="mini-label">POST</span><code>/v1/decisions</code><p data-i18n="quota">Free capacity is limited. On HTTP 429, wait at least one minute before retrying.</p></div>
|
| 35 |
</section>
|
| 36 |
</div>
|
| 37 |
+
<section class="examples"><div class="section-heading"><h2 data-i18n="examplesTitle">Start with a question</h2><span data-i18n="examplesNote">Loads an example. You choose when to send.</span></div><div class="example-grid"><button type="button" data-example="0"><span data-i18n="example0Tag">TEXT</span><strong data-i18n="example0Title">What does this customer need?</strong><p data-i18n="example0Text">Read a message and identify its intent.</p><i aria-hidden="true">↗</i></button><button type="button" data-example="1"><span data-i18n="example1Tag">IMAGE</span><img class="example-image" src="./cookbook/sheep.jpg" alt="" loading="lazy"><strong data-i18n="example1Title">What is this group called?</strong><p data-i18n="example1Text">Look at the animals and choose a name.</p><i aria-hidden="true">↗</i></button><button type="button" data-example="2"><span data-i18n="example2Tag">VIDEO</span><img class="example-image" src="./cookbook/momos-frame.jpg" alt="" loading="lazy"><strong data-i18n="example2Title">What color is the sauce?</strong><p data-i18n="example2Text">Decide from the middle frame of a video.</p><i aria-hidden="true">↗</i></button></div></section>
|
| 38 |
<section class="code-section"><details id="code-details"><summary><span data-i18n="codeTitle">Make it your own · API example</span><span class="code-tag">PYTHON</span></summary><p data-i18n="codeNote">Code follows your current input. Generating a preview does not call the API.</p><div class="code-actions"><button id="preview-code" type="button" data-i18n="preview">Update preview</button><button id="copy-code" type="button" data-i18n="copy">Copy Python</button><span id="copy-status" role="status"></span></div><pre><code id="python-code"></code></pre><details><summary data-i18n="requestPreview">Request JSON · image data omitted</summary><pre id="request-json"></pre></details></details></section>
|
| 39 |
<footer><span>© 2026 Valen Team / 万澜</span><p data-i18n="network">Your browser connects directly to api.valen-team.workers.dev. This Space uses the same API as the website.</p><a href="https://huggingface.co/spaces/Valen-Team/Valen-API/tree/main" target="_blank" rel="noreferrer" data-i18n="source">View source ↗</a></footer>
|
| 40 |
</main>
|
locales.mjs
CHANGED
|
@@ -1,22 +1,31 @@
|
|
| 1 |
export const words = {
|
| 2 |
en: {
|
| 3 |
-
docs:'Documentation ↗', eyebrow:'THE VALEN API PLAYGROUND', title:'A question. A clear decision.', subtitle:'Bring text
|
| 4 |
-
errors:{model:'Choose a supported model and output type.',question:'Enter a question (up to 1000 characters).',context:'Context can be up to 4000 characters.',options:'Enter 2–8 options, one per line, up to 300 characters each.',image:'
|
| 5 |
},
|
| 6 |
zh: {
|
| 7 |
-
docs:'开发者文档 ↗',eyebrow:'VALEN API 体验',title:'一个问题,一次清晰判断。',subtitle:'输入文字
|
| 8 |
-
errors:{model:'请选择支持的模型与输出类型。',question:'请填写问题,最多 1000 字符。',context:'补充背景最多 4000 字符。',options:'请输入 2–8 项,每行一个,每项最多 300 字符。',image:'请选择
|
| 9 |
}
|
| 10 |
};
|
| 11 |
export const examples = {
|
| 12 |
en: [
|
| 13 |
-
['
|
| 14 |
-
['choice','What
|
| 15 |
-
['
|
| 16 |
],
|
| 17 |
zh: [
|
| 18 |
-
['
|
| 19 |
-
['choice','这
|
| 20 |
-
['
|
| 21 |
]
|
| 22 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
export const words = {
|
| 2 |
en: {
|
| 3 |
+
docs:'Documentation ↗', eyebrow:'THE VALEN API PLAYGROUND', title:'A question. A clear decision.', subtitle:'Bring text, an image, or a video. Get probabilities for what comes next.', free:'Free API · No key needed', formats:'Yes / No · Choices · Rating', input:'Your input', model:'Model', task:'Output', predicate:'Yes / No', choice:'Choices', score:'Rating', question:'Question', context:'Context · optional', questionPlaceholder:'What would you like Valen to decide?', contextPlaceholder:'Paste the text to assess, or add some context…', choicesLabel:'Choices · one per line', levelsLabel:'Rating levels · low to high, one per line', optionsHint:'2–8 entries, up to 300 characters each.', attach:'Add an image or video', imageHint:'Images up to 3 MB · MP4 / WebM up to 20 MB', remove:'Remove', submit:'Ask Valen', cancel:'Cancel request', privacy:'On submit, your input is sent directly to the Valen API.', result:'Decision', emptyTitle:'Small input. Useful signal.', emptyText:'Your result and probability distribution will appear here.', scoreNote:'Rating is the probability-weighted level index, starting at 0. Five levels span 0–4.', raw:'Response JSON', quota:'Free capacity is limited. On HTTP 429, wait at least one minute before retrying.', examplesTitle:'Start with a question', examplesNote:'Loads an example. You choose when to send.', example0Tag:'TEXT', example0Title:'What does this customer need?', example0Text:'Read a message and identify its intent.', example1Tag:'IMAGE', example1Title:'What is this group called?', example1Text:'Look at the animals and choose a name.', example2Tag:'VIDEO', example2Title:'What color is the sauce?', example2Text:'Decide from the middle frame of a video.', codeTitle:'Make it your own · API example', codeNote:'Code follows your current input. Generating a preview does not call the API.', preview:'Update preview', copy:'Copy Python', copied:'Copied', copyFailed:'Select the code and copy it manually.', requestPreview:'Request JSON · image data omitted', network:'Your browser connects directly to api.valen-team.workers.dev. This Space uses the same API as the website.', source:'View source ↗', ready:'Ready', busy:'Deciding…', success:'Complete', error:'Try again', reading:'Reading attachment…',videoNote:'Only a JPEG of the middle frame is sent. A single frame cannot establish motion or event order.', cancelled:'Request cancelled.', waiting:'Valen is deciding…', yes:'Yes', no:'No', confidence:'Confidence', modelId:'Returned model ID', noCode:'Enter a valid question and options to generate your API example.',
|
| 4 |
+
errors:{model:'Choose a supported model and output type.',question:'Enter a question (up to 1000 characters).',context:'Context can be up to 4000 characters.',options:'Enter 2–8 options, one per line, up to 300 characters each.',image:'Could not read the attachment. Choose JPG, PNG, WebP, or a browser-playable MP4 / WebM.',size:'Images can be up to 3 MB; videos up to 20 MB.',response:'The API returned an unexpected response. Please retry later.',network:'Could not connect to the API. Check your network or proxy and try again.',timeout:'The request timed out after two minutes. Please retry later.',http_400:'HTTP 400 · The API rejected this request. Check the question and options.',http_401:'HTTP 401 · API authentication failed.',http_403:'HTTP 403 · This website is not allowed to call the API.',http_413:'HTTP 413 · The request is too large.',http_429:'HTTP 429 · Free API capacity is limited. Wait at least one minute before retrying.',http_502:'HTTP 502 · The model service could not complete the request.',http_503:'HTTP 503 · The API is not configured or is unavailable.'}
|
| 5 |
},
|
| 6 |
zh: {
|
| 7 |
+
docs:'开发者文档 ↗',eyebrow:'VALEN API 体验',title:'一个问题,一次清晰判断。',subtitle:'输入文字、图片或视频,让下一步决策有据可依。',free:'免费 API · 无需 Key',formats:'是 / 否 · 选择 · 评分',input:'你的输入',model:'模型',task:'输出类型',predicate:'是 / 否',choice:'选择答案',score:'评分',question:'问题',context:'补充背景 · 可选',questionPlaceholder:'你想让 Valen 判断什么?',contextPlaceholder:'粘贴需要判断的文字,或补充相关信息…',choicesLabel:'候选项 · 每行一个',levelsLabel:'评分档位 · 从低到高,每行一个',optionsHint:'2–8 项,每项最多 300 字符。',attach:'添加图片或视频',imageHint:'图片最大 3 MB · MP4 / WebM 视频最大 20 MB',remove:'移除',submit:'提交判断',cancel:'取消请求',privacy:'提交后,你的输入会直接发送至 Valen API。',result:'判断结果',emptyTitle:'简单输入,清晰信号。',emptyText:'判断结果与各个选项的概率将在这里显示。',scoreNote:'评分按从 0 开始的档位索引进行概率加权。五个档位对应 0–4 分。',raw:'原始响应 JSON',quota:'免费 API 容量有限。遇到 HTTP 429,请至少等待一分钟后重试。',examplesTitle:'从一个问题开始',examplesNote:'点击填入示例,提交时才会请求模型。',example0Tag:'纯文本',example0Title:'这位客户想做什么?',example0Text:'读一段客户消息,判断它表达的意图。',example1Tag:'图片',example1Title:'这群动物应该怎样称呼?',example1Text:'看一张动物图片,选择正确的群体名称。',example2Tag:'视频',example2Title:'左边的酱料是什么颜色?',example2Text:'提取视频中间帧,根据可见画面判断。',codeTitle:'接入你的应用 · API 示例',codeNote:'代码根据当前输入生成。生成预览不会调用 API。',preview:'更新预览',copy:'复制 Python',copied:'已复制',copyFailed:'请选中代码后手动复制。',requestPreview:'请求 JSON · 已省略图片数据',network:'浏览器直接连接 api.valen-team.workers.dev。此 Space 与官网共用同一个 API。',source:'查看源码 ↗',ready:'等待提问',busy:'正在判断…',success:'已完成',error:'请求未完成',reading:'正在读取附件…',videoNote:'只发送视频中间帧的 JPEG 图片。单帧无法判断运动或事件顺序。',cancelled:'请求已取消。',waiting:'Valen 正在判断…',yes:'是',no:'否',confidence:'置信度',modelId:'返回的模型标识',noCode:'填写有效的问题与选项后,即可生成对应的 API 示例。',
|
| 8 |
+
errors:{model:'请选择支持的模型与输出类型。',question:'请填写问题,最多 1000 字符。',context:'补充背景最多 4000 字符。',options:'请输入 2–8 项,每行一个,每项最多 300 字符。',image:'无法读取附件。请选择 JPG、PNG、WebP,或浏览器可播放的 MP4 / WebM。',size:'图片最大 3 MB,视频最大 20 MB。',response:'API 返回的格式异常,请稍后重试。',network:'无法连接 API,请检查当前网络或代理后重试。',timeout:'请求等待超过两分钟,请稍后重试。',http_400:'HTTP 400 · API 拒绝了请求,请检查问题与选项。',http_401:'HTTP 401 · API 鉴权失败。',http_403:'HTTP 403 · 当前网站尚未获准调用 API。',http_413:'HTTP 413 · 请求体积超出限制。',http_429:'HTTP 429 · 免费 API 额度受限,请至少等待一分钟后重试。',http_502:'HTTP 502 · 模型服务暂时无法完成请求。',http_503:'HTTP 503 · API 尚未配置或暂不可用。'}
|
| 9 |
}
|
| 10 |
};
|
| 11 |
export const examples = {
|
| 12 |
en: [
|
| 13 |
+
['choice', "Which intent does the user's message express?", 'I moved. Can you ship my order to 12 Elm Street instead of the old address?', 'Asks about a charge, invoice or payment\nWants to cancel an order\nWants to change the delivery address\nReceived an item that is broken or damaged\nWants to know where an order is or when it arrives'],
|
| 14 |
+
['choice', 'What is a group of these animals called?', 'Answer using only the visible image.', 'pride\nflock\nclowder\ngang'],
|
| 15 |
+
['choice', 'What is the color of the sauce in the foil container on the left?', 'Answer using only the visible image.', 'Brown\nWhite\nRed\nOrange']
|
| 16 |
],
|
| 17 |
zh: [
|
| 18 |
+
['choice', '这条客户消息表达了什么意图?', '我搬家了,可以把订单寄到榆树街 12 号,而不是原来的地址吗?', '咨询费用、发票或付款\n取消订单\n更改收货地址\n反馈收到的商品破损\n查询订单位置或预计送达时间'],
|
| 19 |
+
['choice', '图片中的这群动物应该怎样称呼?', '请仅根据图片中可见的内容回答问题。', '狮群\n羊群\n猫群\n猴群'],
|
| 20 |
+
['choice', '左边铝箔容器里的酱料是什么颜色?', '请仅根据视频画面中可见的内容回答问题。', '棕色\n白色\n红色\n橙色']
|
| 21 |
]
|
| 22 |
};
|
| 23 |
+
export const exampleMedia = [
|
| 24 |
+
null,
|
| 25 |
+
{kind:'image',src:'./cookbook/sheep.jpg',name:'sheep.jpg'},
|
| 26 |
+
{kind:'video',src:'./cookbook/momos.mp4',name:'momos.mp4'}
|
| 27 |
+
];
|
| 28 |
+
export const ratingOptions = {
|
| 29 |
+
en:'Very low\nLow\nModerate\nHigh\nVery high',
|
| 30 |
+
zh:'很低\n较低\n中等\n较高\n很高'
|
| 31 |
+
};
|
media.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Browser-side preprocessing adapted from the Valen website's media.ts.
|
| 2 |
+
function once(target, event, signal) {
|
| 3 |
+
return new Promise((resolve, reject) => {
|
| 4 |
+
const finish = error => {
|
| 5 |
+
clearTimeout(timer);target.removeEventListener(event, success);target.removeEventListener('error', failure);
|
| 6 |
+
signal.removeEventListener('abort', abort);error ? reject(error) : resolve();
|
| 7 |
+
};
|
| 8 |
+
const success = () => finish();
|
| 9 |
+
const failure = () => finish(new Error('image'));
|
| 10 |
+
const abort = () => finish(new DOMException('Aborted','AbortError'));
|
| 11 |
+
const timer = setTimeout(failure,20000);
|
| 12 |
+
target.addEventListener(event,success,{once:true});target.addEventListener('error',failure,{once:true});
|
| 13 |
+
signal.addEventListener('abort',abort,{once:true});if(signal.aborted)abort();
|
| 14 |
+
});
|
| 15 |
+
}
|
| 16 |
+
function jpeg(source, width, height) {
|
| 17 |
+
if(!width||!height)throw new Error('image');
|
| 18 |
+
const scale=Math.min(1,1536/Math.max(width,height));
|
| 19 |
+
const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.round(width*scale));canvas.height=Math.max(1,Math.round(height*scale));
|
| 20 |
+
const ctx=canvas.getContext('2d');if(!ctx)throw new Error('image');
|
| 21 |
+
ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(source,0,0,canvas.width,canvas.height);
|
| 22 |
+
return canvas.toDataURL('image/jpeg',.85);
|
| 23 |
+
}
|
| 24 |
+
export async function readMedia(file, signal) {
|
| 25 |
+
const videoType=['video/mp4','video/webm'].includes(file.type);
|
| 26 |
+
if(!videoType&&!['image/jpeg','image/png','image/webp'].includes(file.type))throw new Error('image');
|
| 27 |
+
if(file.size>(videoType?20:3)*1000000)throw new Error('size');
|
| 28 |
+
const url=URL.createObjectURL(file);
|
| 29 |
+
try {
|
| 30 |
+
if(!videoType) {
|
| 31 |
+
const image=new Image();const loaded=once(image,'load',signal);image.src=url;await loaded;
|
| 32 |
+
return {kind:'image',name:file.name,image:jpeg(image,image.naturalWidth,image.naturalHeight),objectUrl:url};
|
| 33 |
+
}
|
| 34 |
+
const video=document.createElement('video');video.muted=true;video.playsInline=true;video.preload='auto';
|
| 35 |
+
try {
|
| 36 |
+
const loaded=once(video,'loadeddata',signal);video.src=url;await loaded;
|
| 37 |
+
if(!Number.isFinite(video.duration)||video.duration<=0)throw new Error('image');
|
| 38 |
+
if(video.duration/2>.01){const seeked=once(video,'seeked',signal);video.currentTime=video.duration/2;await seeked;}
|
| 39 |
+
return {kind:'video',name:file.name,image:jpeg(video,video.videoWidth,video.videoHeight),video:url,objectUrl:url};
|
| 40 |
+
} finally {video.removeAttribute('src');video.load();}
|
| 41 |
+
} catch(error) {URL.revokeObjectURL(url);throw error;}
|
| 42 |
+
}
|
style.css
CHANGED
|
@@ -1,3 +1,5 @@
|
|
| 1 |
@import url('./assets/modern-tokens.css');
|
| 2 |
@font-face{font-family:'DM Sans';src:url('./assets/dmsans.woff2') format('woff2');font-display:swap}
|
| 3 |
*{box-sizing:border-box}body{margin:0;background:var(--bg-doc);color:var(--text);font:15px/1.5 var(--font-body)}body[data-theme=dark]{--bg-doc:#101319;--bg:#101319;--surface:#1b202a;--text:#e4e9f3;--heading:#f0f4ff;--text-muted:#a1adbf;--text-faint:#8794a7;--border:#30394a;--primary:#8aa5ff;--primary-soft:#202e53;--green-bg:#173828;--green:#83d5a0;--red:#ff9b9b;--red-bg:#3b2027}button,input,select,textarea{font:inherit}button,a,input,select,textarea,summary{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button:disabled{cursor:wait;opacity:.55}a{color:inherit;text-decoration:none}a:hover{color:var(--primary)}button,select,textarea{outline-offset:4px}button:focus-visible,a:focus-visible,summary:focus-visible{outline:2px solid var(--primary);outline-offset:4px}[hidden]{display:none!important}.nav{height:72px;display:flex;justify-content:space-between;align-items:center;max-width:1200px;margin:auto;padding:0 28px;border-bottom:1px solid var(--border)}.brand{display:flex;align-items:center;gap:9px}.brand img{width:36px;height:36px;object-fit:cover;border-radius:8px}.brand strong{font-size:27px;letter-spacing:-1.3px;color:var(--heading)}.brand span{font:11px var(--font-mono);color:var(--text-muted);border-left:1px solid var(--border);padding-left:14px;margin-left:6px}.nav nav{display:flex;align-items:center;gap:22px;font-size:13px}.nav button{background:none;color:var(--text);border:0;padding:6px}.nav #theme{font-size:22px}main{max-width:1200px;padding:0 28px;margin:auto}.intro{padding:55px 0 34px}.eyebrow{font-size:11px;letter-spacing:2px;color:var(--primary);font-weight:700}h1{font-size:clamp(30px,4.5vw,46px);line-height:1.15;color:var(--heading);letter-spacing:-1.5px;margin:15px 0}h2,h3,p{margin-top:0}.intro p{color:var(--text-muted);font-size:17px;margin-bottom:20px}.intro-meta{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-muted)}.dot{height:6px;width:6px;background:var(--green);border-radius:50%}.divider{padding:0 8px;color:var(--text-faint)}.workspace{display:grid;grid-template-columns:1fr 1fr;gap:24px}.panel{border:1px solid var(--border);border-radius:16px;padding:25px;min-width:0}.panel-heading{display:flex;align-items:center;gap:10px;margin-bottom:22px}.panel-heading h2{font-size:16px;margin:0;letter-spacing:-.2px}.step{font:11px var(--font-mono);color:var(--text-faint)}fieldset{border:0;padding:0;margin:0;min-width:0}label{display:block;margin-bottom:17px}label>span{display:block;font-size:12px;font-weight:600;margin-bottom:7px}label small{color:var(--text-muted);font-size:11px;font-weight:400;margin-left:5px}.two-columns{display:grid;grid-template-columns:1fr 1fr;gap:15px}select,textarea{width:100%;border:1px solid var(--border);background:var(--bg-doc);border-radius:8px;color:var(--text);padding:10px 12px;font-size:13px}textarea{resize:vertical;min-height:75px;line-height:1.6;display:block}textarea::placeholder{color:var(--text-faint)}select:focus,textarea:focus{outline:2px solid var(--primary-soft);border-color:var(--primary)}.attachment{border:1px dashed var(--border-strong,var(--border));border-radius:10px;margin-bottom:18px;padding:12px}.upload-label{display:flex;gap:10px;align-items:center;margin:0;cursor:pointer}.upload-label span{margin:0}.upload-label strong{font-size:12px}.upload-label small{display:block;margin:2px 0 0}.upload-icon{font-size:26px;color:var(--primary)}#image{width:100%;font-size:11px;margin-top:10px;color:var(--text-muted)}#image::file-selector-button{border:1px solid var(--border);padding:4px 8px;border-radius:5px;background:var(--surface);color:var(--text);margin-right:8px}#image-preview{display:flex;align-items:center;gap:12px;padding-top:10px}#thumbnail{width:48px;height:48px;object-fit:cover;border-radius:5px}#image-name{min-width:0;overflow-wrap:anywhere;font-size:12px}#remove-image{margin-left:auto;border:0;background:none;color:var(--text-muted);font-size:12px}.primary{width:100%;display:flex;justify-content:center;gap:10px;background:var(--primary);color:white;border:0;border-radius:8px;padding:12px;font-weight:600;font-size:14px}.primary:hover{filter:brightness(.95)}body[data-theme=dark] .primary{color:#101319}.privacy{font-size:11px;text-align:center;color:var(--text-muted);margin:12px 0 0}.cancel{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:6px 12px;border-radius:6px;display:block;margin:10px auto 0;font-size:12px}.output-panel{display:flex;flex-direction:column}.badge{margin-left:auto;color:var(--text-muted);background:var(--surface);padding:4px 9px;border-radius:100px;font-size:10px}.badge[data-state=success]{color:var(--green);background:var(--green-bg)}.badge[data-state=error]{color:var(--red);background:var(--red-bg)}.badge[data-state=busy]{color:var(--primary);background:var(--primary-soft)}#status{font-size:13px;color:var(--text-muted);overflow-wrap:anywhere}#status.error{color:var(--red);padding:12px;background:var(--red-bg);border-radius:8px;margin-bottom:18px}#empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:290px;padding:25px 10px}.empty-mark{color:var(--primary);font-size:26px;background:var(--primary-soft);width:58px;height:58px;display:grid;place-items:center;border-radius:16px;margin-bottom:22px;font-weight:600}#empty h3{font-size:19px;letter-spacing:-.4px;margin-bottom:8px}#empty p{max-width:270px;color:var(--text-muted);font-size:13px}#answer{padding:8px 0 24px}#decision-title{font-size:26px;line-height:1.3;overflow-wrap:anywhere;color:var(--heading);margin:12px 0 26px}.prob-row{margin:0 0 16px}.prob-label{display:flex;justify-content:space-between;gap:15px;margin-bottom:6px;font-size:13px}.prob-label span{overflow-wrap:anywhere}.prob-label strong{font:12px var(--font-mono);white-space:nowrap}progress{display:block;width:100%;height:6px;appearance:none;border:0;border-radius:6px;overflow:hidden;background:var(--surface)}progress::-webkit-progress-bar{background:var(--surface)}progress::-webkit-progress-value{background:var(--primary);border-radius:6px}progress::-moz-progress-bar{background:var(--primary);border-radius:6px}.note{font-size:12px;color:var(--text-muted);margin:20px 0 10px}.model-id{font:10px/1.5 var(--font-mono);color:var(--text-faint);overflow-wrap:anywhere}.api-note{border-top:1px solid var(--border);padding-top:18px;margin-top:auto}.mini-label{color:var(--green);background:var(--green-bg);font:10px var(--font-mono);padding:3px 5px;border-radius:4px;margin-right:7px}.api-note code{font:11px var(--font-mono)}.api-note p{font-size:11px;color:var(--text-muted);margin:10px 0 0}.examples{margin-top:35px}.section-heading{display:flex;justify-content:space-between;align-items:center;gap:15px;margin-bottom:15px}.section-heading h2{font-size:16px;margin:0}.section-heading>span{font-size:11px;color:var(--text-muted)}.example-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.example-grid button{position:relative;background:var(--bg-doc);border:1px solid var(--border);border-radius:12px;padding:19px;text-align:left;color:var(--text)}.example-grid button:hover{border-color:var(--primary);background:var(--primary-soft)}.example-grid button>span{display:block;font-size:9px;letter-spacing:1.1px;color:var(--text-muted);margin-bottom:13px}.example-grid strong{font-size:15px;font-weight:600}.example-grid p{font-size:12px;color:var(--text-muted);margin:6px 0 0}.example-grid i{position:absolute;right:17px;top:15px;color:var(--text-muted);font-style:normal}.code-section{margin:32px 0;border:1px solid var(--border);padding:18px 22px;border-radius:12px}summary{cursor:pointer;font-size:12px;font-weight:600;line-height:1.6}summary::marker{color:var(--text-muted)}.code-section>details>summary{font-size:14px}.code-tag{float:right;font:9px var(--font-mono);color:var(--text-muted);padding-top:4px}.code-section p{font-size:12px;color:var(--text-muted);margin:16px 0}.code-actions{display:flex;gap:8px;align-items:center}.code-actions button{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:11px}#copy-status{font-size:11px;color:var(--text-muted)}pre{background:var(--surface);border-radius:8px;padding:16px;font:11px/1.65 var(--font-mono);overflow:auto;max-height:420px;white-space:pre-wrap;overflow-wrap:anywhere}.code-section>details>pre{background:var(--code-bg);color:#e2e6ef;margin-top:15px}footer{border-top:1px solid var(--border);padding:20px 0 30px;display:flex;align-items:flex-start;gap:30px;font-size:10px;color:var(--text-muted)}footer p{margin:0;max-width:550px}footer>a{margin-left:auto;white-space:nowrap}footer>span{white-space:nowrap}@media(max-width:760px){.nav{padding:0 18px;height:62px}.nav nav{gap:12px}.nav nav>a{font-size:11px}main{padding:0 18px}.intro{padding:35px 0 26px}.intro p{font-size:14px}.intro-meta{font-size:10px;flex-wrap:wrap}.workspace{grid-template-columns:1fr;gap:16px}.panel{padding:19px}.example-grid{grid-template-columns:1fr}.example-grid button{padding:15px}.example-grid button>span{margin-bottom:6px}.section-heading{align-items:flex-start;flex-direction:column;gap:5px}#empty{min-height:200px}footer{flex-direction:column;gap:12px}footer>a{margin-left:0}.code-section{padding:15px}.code-tag{display:none}}
|
|
|
|
|
|
|
|
|
| 1 |
@import url('./assets/modern-tokens.css');
|
| 2 |
@font-face{font-family:'DM Sans';src:url('./assets/dmsans.woff2') format('woff2');font-display:swap}
|
| 3 |
*{box-sizing:border-box}body{margin:0;background:var(--bg-doc);color:var(--text);font:15px/1.5 var(--font-body)}body[data-theme=dark]{--bg-doc:#101319;--bg:#101319;--surface:#1b202a;--text:#e4e9f3;--heading:#f0f4ff;--text-muted:#a1adbf;--text-faint:#8794a7;--border:#30394a;--primary:#8aa5ff;--primary-soft:#202e53;--green-bg:#173828;--green:#83d5a0;--red:#ff9b9b;--red-bg:#3b2027}button,input,select,textarea{font:inherit}button,a,input,select,textarea,summary{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button:disabled{cursor:wait;opacity:.55}a{color:inherit;text-decoration:none}a:hover{color:var(--primary)}button,select,textarea{outline-offset:4px}button:focus-visible,a:focus-visible,summary:focus-visible{outline:2px solid var(--primary);outline-offset:4px}[hidden]{display:none!important}.nav{height:72px;display:flex;justify-content:space-between;align-items:center;max-width:1200px;margin:auto;padding:0 28px;border-bottom:1px solid var(--border)}.brand{display:flex;align-items:center;gap:9px}.brand img{width:36px;height:36px;object-fit:cover;border-radius:8px}.brand strong{font-size:27px;letter-spacing:-1.3px;color:var(--heading)}.brand span{font:11px var(--font-mono);color:var(--text-muted);border-left:1px solid var(--border);padding-left:14px;margin-left:6px}.nav nav{display:flex;align-items:center;gap:22px;font-size:13px}.nav button{background:none;color:var(--text);border:0;padding:6px}.nav #theme{font-size:22px}main{max-width:1200px;padding:0 28px;margin:auto}.intro{padding:55px 0 34px}.eyebrow{font-size:11px;letter-spacing:2px;color:var(--primary);font-weight:700}h1{font-size:clamp(30px,4.5vw,46px);line-height:1.15;color:var(--heading);letter-spacing:-1.5px;margin:15px 0}h2,h3,p{margin-top:0}.intro p{color:var(--text-muted);font-size:17px;margin-bottom:20px}.intro-meta{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-muted)}.dot{height:6px;width:6px;background:var(--green);border-radius:50%}.divider{padding:0 8px;color:var(--text-faint)}.workspace{display:grid;grid-template-columns:1fr 1fr;gap:24px}.panel{border:1px solid var(--border);border-radius:16px;padding:25px;min-width:0}.panel-heading{display:flex;align-items:center;gap:10px;margin-bottom:22px}.panel-heading h2{font-size:16px;margin:0;letter-spacing:-.2px}.step{font:11px var(--font-mono);color:var(--text-faint)}fieldset{border:0;padding:0;margin:0;min-width:0}label{display:block;margin-bottom:17px}label>span{display:block;font-size:12px;font-weight:600;margin-bottom:7px}label small{color:var(--text-muted);font-size:11px;font-weight:400;margin-left:5px}.two-columns{display:grid;grid-template-columns:1fr 1fr;gap:15px}select,textarea{width:100%;border:1px solid var(--border);background:var(--bg-doc);border-radius:8px;color:var(--text);padding:10px 12px;font-size:13px}textarea{resize:vertical;min-height:75px;line-height:1.6;display:block}textarea::placeholder{color:var(--text-faint)}select:focus,textarea:focus{outline:2px solid var(--primary-soft);border-color:var(--primary)}.attachment{border:1px dashed var(--border-strong,var(--border));border-radius:10px;margin-bottom:18px;padding:12px}.upload-label{display:flex;gap:10px;align-items:center;margin:0;cursor:pointer}.upload-label span{margin:0}.upload-label strong{font-size:12px}.upload-label small{display:block;margin:2px 0 0}.upload-icon{font-size:26px;color:var(--primary)}#image{width:100%;font-size:11px;margin-top:10px;color:var(--text-muted)}#image::file-selector-button{border:1px solid var(--border);padding:4px 8px;border-radius:5px;background:var(--surface);color:var(--text);margin-right:8px}#image-preview{display:flex;align-items:center;gap:12px;padding-top:10px}#thumbnail{width:48px;height:48px;object-fit:cover;border-radius:5px}#image-name{min-width:0;overflow-wrap:anywhere;font-size:12px}#remove-image{margin-left:auto;border:0;background:none;color:var(--text-muted);font-size:12px}.primary{width:100%;display:flex;justify-content:center;gap:10px;background:var(--primary);color:white;border:0;border-radius:8px;padding:12px;font-weight:600;font-size:14px}.primary:hover{filter:brightness(.95)}body[data-theme=dark] .primary{color:#101319}.privacy{font-size:11px;text-align:center;color:var(--text-muted);margin:12px 0 0}.cancel{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:6px 12px;border-radius:6px;display:block;margin:10px auto 0;font-size:12px}.output-panel{display:flex;flex-direction:column}.badge{margin-left:auto;color:var(--text-muted);background:var(--surface);padding:4px 9px;border-radius:100px;font-size:10px}.badge[data-state=success]{color:var(--green);background:var(--green-bg)}.badge[data-state=error]{color:var(--red);background:var(--red-bg)}.badge[data-state=busy]{color:var(--primary);background:var(--primary-soft)}#status{font-size:13px;color:var(--text-muted);overflow-wrap:anywhere}#status.error{color:var(--red);padding:12px;background:var(--red-bg);border-radius:8px;margin-bottom:18px}#empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:290px;padding:25px 10px}.empty-mark{color:var(--primary);font-size:26px;background:var(--primary-soft);width:58px;height:58px;display:grid;place-items:center;border-radius:16px;margin-bottom:22px;font-weight:600}#empty h3{font-size:19px;letter-spacing:-.4px;margin-bottom:8px}#empty p{max-width:270px;color:var(--text-muted);font-size:13px}#answer{padding:8px 0 24px}#decision-title{font-size:26px;line-height:1.3;overflow-wrap:anywhere;color:var(--heading);margin:12px 0 26px}.prob-row{margin:0 0 16px}.prob-label{display:flex;justify-content:space-between;gap:15px;margin-bottom:6px;font-size:13px}.prob-label span{overflow-wrap:anywhere}.prob-label strong{font:12px var(--font-mono);white-space:nowrap}progress{display:block;width:100%;height:6px;appearance:none;border:0;border-radius:6px;overflow:hidden;background:var(--surface)}progress::-webkit-progress-bar{background:var(--surface)}progress::-webkit-progress-value{background:var(--primary);border-radius:6px}progress::-moz-progress-bar{background:var(--primary);border-radius:6px}.note{font-size:12px;color:var(--text-muted);margin:20px 0 10px}.model-id{font:10px/1.5 var(--font-mono);color:var(--text-faint);overflow-wrap:anywhere}.api-note{border-top:1px solid var(--border);padding-top:18px;margin-top:auto}.mini-label{color:var(--green);background:var(--green-bg);font:10px var(--font-mono);padding:3px 5px;border-radius:4px;margin-right:7px}.api-note code{font:11px var(--font-mono)}.api-note p{font-size:11px;color:var(--text-muted);margin:10px 0 0}.examples{margin-top:35px}.section-heading{display:flex;justify-content:space-between;align-items:center;gap:15px;margin-bottom:15px}.section-heading h2{font-size:16px;margin:0}.section-heading>span{font-size:11px;color:var(--text-muted)}.example-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.example-grid button{position:relative;background:var(--bg-doc);border:1px solid var(--border);border-radius:12px;padding:19px;text-align:left;color:var(--text)}.example-grid button:hover{border-color:var(--primary);background:var(--primary-soft)}.example-grid button>span{display:block;font-size:9px;letter-spacing:1.1px;color:var(--text-muted);margin-bottom:13px}.example-grid strong{font-size:15px;font-weight:600}.example-grid p{font-size:12px;color:var(--text-muted);margin:6px 0 0}.example-grid i{position:absolute;right:17px;top:15px;color:var(--text-muted);font-style:normal}.code-section{margin:32px 0;border:1px solid var(--border);padding:18px 22px;border-radius:12px}summary{cursor:pointer;font-size:12px;font-weight:600;line-height:1.6}summary::marker{color:var(--text-muted)}.code-section>details>summary{font-size:14px}.code-tag{float:right;font:9px var(--font-mono);color:var(--text-muted);padding-top:4px}.code-section p{font-size:12px;color:var(--text-muted);margin:16px 0}.code-actions{display:flex;gap:8px;align-items:center}.code-actions button{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:11px}#copy-status{font-size:11px;color:var(--text-muted)}pre{background:var(--surface);border-radius:8px;padding:16px;font:11px/1.65 var(--font-mono);overflow:auto;max-height:420px;white-space:pre-wrap;overflow-wrap:anywhere}.code-section>details>pre{background:var(--code-bg);color:#e2e6ef;margin-top:15px}footer{border-top:1px solid var(--border);padding:20px 0 30px;display:flex;align-items:flex-start;gap:30px;font-size:10px;color:var(--text-muted)}footer p{margin:0;max-width:550px}footer>a{margin-left:auto;white-space:nowrap}footer>span{white-space:nowrap}@media(max-width:760px){.nav{padding:0 18px;height:62px}.nav nav{gap:12px}.nav nav>a{font-size:11px}main{padding:0 18px}.intro{padding:35px 0 26px}.intro p{font-size:14px}.intro-meta{font-size:10px;flex-wrap:wrap}.workspace{grid-template-columns:1fr;gap:16px}.panel{padding:19px}.example-grid{grid-template-columns:1fr}.example-grid button{padding:15px}.example-grid button>span{margin-bottom:6px}.section-heading{align-items:flex-start;flex-direction:column;gap:5px}#empty{min-height:200px}footer{flex-direction:column;gap:12px}footer>a{margin-left:0}.code-section{padding:15px}.code-tag{display:none}}
|
| 4 |
+
|
| 5 |
+
#video-preview{display:block;width:100%;max-height:240px;border-radius:8px;margin-top:12px;background:#000}.example-image{display:block;width:100%;height:110px;object-fit:cover;border-radius:7px;margin:0 0 12px}.example-grid button{align-self:stretch}.example-grid strong{display:block}#video-note{margin:10px 0 0;font-size:11px}
|