Spaces:
Sleeping
Sleeping
| // Normalizes raw entries from data/prompts.json (relative_50_v3_prompts_newid.json) | |
| // into the shape the UI works with. See T2AV_guideline_final_updated.html for the | |
| // axis vocabulary (A / V / AVB / TC_REL / TC_COUNT / CC) this is built around. | |
| const MODALITY_LABELS = { | |
| speech: "Speech", | |
| silent_action: "Silent action", | |
| sounding_action: "Sounding action" | |
| }; | |
| // Which of the per-event axes apply to a given event modality. | |
| // Silent actions have no audio component, so no A and no AVB (per guideline s2/s4). | |
| function axesForModality(modality) { | |
| if (modality === "silent_action") { | |
| return { a: false, v: true, avb: false }; | |
| } | |
| return { a: true, v: true, avb: true }; | |
| } | |
| function describeEvent(event) { | |
| if (event.modality === "speech") { | |
| return `Speech: say "${event.text}"`; | |
| } | |
| const sideAndPart = [event.body_side, event.body_part].filter(Boolean).join(" "); | |
| if (event.modality === "silent_action") { | |
| const detail = sideAndPart ? ` (${sideAndPart})` : ""; | |
| return `Silent action: ${event.action}${detail}`; | |
| } | |
| // sounding_action | |
| const actingSide = [event.acting_body_side, event.acting_body_part].filter(Boolean).join(" "); | |
| const targetSide = [event.target_body_side, event.target_body_part].filter(Boolean).join(" "); | |
| const detail = [actingSide && `with ${actingSide}`, targetSide && `on ${targetSide}`] | |
| .filter(Boolean) | |
| .join(" "); | |
| return `Sounding action: ${event.action}${detail ? ` (${detail})` : ""}`; | |
| } | |
| function normalizeEvent(event) { | |
| return { | |
| id: event.id, | |
| modality: event.modality, | |
| modalityLabel: MODALITY_LABELS[event.modality] || event.modality, | |
| actor: event.actor, | |
| label: describeEvent(event), | |
| definition: event.definition, | |
| axes: axesForModality(event.modality) | |
| }; | |
| } | |
| // Per-event expected count, derived from the temporal_constraint's value shape. | |
| // Defaults to 1 for any event not explicitly given a count (relation types | |
| // without counting — synchronization/order — always expect exactly one). | |
| function expectedCountsFromConstraint(tc, eventIds) { | |
| const counts = Object.fromEntries(eventIds.map((id) => [id, 1])); | |
| if (!tc || !tc.value) return counts; | |
| if (tc.relation_type === "sync_count" && Array.isArray(tc.value.events)) { | |
| tc.value.events.forEach((id) => { | |
| counts[id] = tc.value.count; | |
| }); | |
| } else if (tc.relation_type === "order_count" && tc.value.event_counts) { | |
| Object.entries(tc.value.event_counts).forEach(([id, count]) => { | |
| counts[id] = count; | |
| }); | |
| } | |
| return counts; | |
| } | |
| function describeTemporalConstraint(tc, eventsById) { | |
| if (!tc || !tc.value) return "No temporal constraint specified."; | |
| const label = (id) => (eventsById[id] ? eventsById[id].label : id); | |
| switch (tc.relation_type) { | |
| case "synchronization": | |
| return `Synchronization: ${tc.value.events.map(label).join(" + ")} must all occur at the same moment.`; | |
| case "sync_count": | |
| return `Synchronization x${tc.value.count}: ${tc.value.events | |
| .map(label) | |
| .join(" + ")} must occur together, repeated exactly ${tc.value.count} times.`; | |
| case "order": | |
| return `Order: ${tc.value.sequence.map(label).join(" -> then -> ")}.`; | |
| case "order_count": { | |
| const parts = tc.value.ordered_events.map( | |
| (id) => `${label(id)} x${tc.value.event_counts[id] ?? 1}` | |
| ); | |
| return `Order: ${parts.join(" -> then -> ")}.`; | |
| } | |
| default: | |
| return `${tc.relation_type}: ${JSON.stringify(tc.value)}`; | |
| } | |
| } | |
| export function normalizePromptItem(raw) { | |
| const events = raw.events.map(normalizeEvent); | |
| const eventsById = Object.fromEntries(events.map((e) => [e.id, e])); | |
| const expectedCounts = expectedCountsFromConstraint(raw.temporal_constraint, events.map((e) => e.id)); | |
| return { | |
| itemId: raw.item_id, | |
| canonicalId: raw.canonical_item_id, | |
| modalityCombination: raw.modality_combination, | |
| prompt: raw.generated_prompt, | |
| sceneHint: raw.scene_hint, | |
| cameraHint: raw.camera_hint, | |
| events, | |
| temporalConstraint: { | |
| relationType: raw.temporal_constraint.relation_type, | |
| timingType: raw.temporal_constraint.timing_type, | |
| value: raw.temporal_constraint.value, | |
| expectedCounts, | |
| description: describeTemporalConstraint(raw.temporal_constraint, eventsById) | |
| } | |
| }; | |
| } | |
| export function normalizePromptList(rawList) { | |
| return rawList.map(normalizePromptItem); | |
| } | |
| // Creates an empty annotations object matching a normalized item's applicable | |
| // axes, so the UI never has to special-case "does this event have an A field". | |
| export function createEmptyAnnotations(item) { | |
| const events = Object.fromEntries( | |
| item.events.map((event) => { | |
| const fields = {}; | |
| if (event.axes.a) fields.a = { label: "", rationale: "" }; | |
| if (event.axes.v) fields.v = { label: "", rationale: "" }; | |
| if (event.axes.avb) fields.avb = { label: "", rationale: "" }; | |
| fields.tcCount = { value: "", rationale: "" }; | |
| return [event.id, fields]; | |
| }) | |
| ); | |
| return { | |
| events, | |
| video: { | |
| tcRel: { label: "", rationale: "" }, | |
| cc: { label: "", rationale: "" } | |
| } | |
| }; | |
| } | |