Spaces:
Paused
Paused
| <!-- CONFIG_HASH: a26ad7a42ddec00ebb17dd83674016ae --> | |
| {# Default ui_lang values if not provided by context processor #} | |
| {% if ui_lang is not defined %} | |
| {% set ui_lang = {'next_button': 'Next', 'previous_button': 'Previous', 'jump_prev_unannotated': 'Previous unannotated', 'jump_next_unannotated': 'Next unannotated', 'labeled_badge': 'Labeled', 'not_labeled_badge': 'Not labeled', 'submit_button': 'Submit', 'progress_label': 'Progress', 'go_button': 'Go', 'logout': 'Logout', 'loading': 'Loading annotation interface...', 'error_heading': 'Error', 'retry_button': 'Retry', 'adjudicate': 'Adjudicate', 'codebook': 'Codebook', 'instructions_heading': 'Instructions', 'text_to_annotate': 'Text to Annotate:', 'video_to_annotate': 'Video to Annotate:', 'audio_to_annotate': 'Audio to Annotate:', 'powered_by': 'Powered by', 'cite_us': 'Cite Us', 'html_lang': 'en', 'html_dir': 'ltr'} %} | |
| {% endif %} | |
| <html lang="{{ ui_lang.html_lang|default('en') }}" dir="{{ ui_lang.html_dir|default('ltr') }}"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Agent Trajectory Evaluation</title> | |
| <!-- Bootstrap CSS --> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <!-- Font Awesome --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <!-- Custom Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> | |
| <!-- Span Annotation Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='span-styles.css') }}"> | |
| {% set frontend_assets = frontend_assets | default({}) %} | |
| <!-- Image Annotation Styles --> | |
| {% if frontend_assets.image_annotation | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='image-annotation.css') }}?v=2"> | |
| {% endif %} | |
| <!-- Audio Annotation Styles --> | |
| {% if frontend_assets.audio_annotation | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='audio-annotation.css') }}?v=2"> | |
| {% endif %} | |
| <!-- Video Annotation Styles --> | |
| {% if frontend_assets.video_annotation | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='video-annotation.css') }}"> | |
| {% endif %} | |
| <!-- Span Link Styles --> | |
| {% if frontend_assets.span_link | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='span-link-styles.css') }}"> | |
| {% endif %} | |
| <!-- Event Annotation Styles --> | |
| {% if frontend_assets.event_annotation | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='event-annotation.css') }}"> | |
| {% endif %} | |
| <!-- Spreadsheet Display Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='spreadsheet-display.css') }}"> | |
| <!-- Coding Trace Display Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='coding-trace.css') }}"> | |
| <!-- Live Coding Agent Viewer --> | |
| {% if frontend_assets.live_coding_agent | default(false) %} | |
| <script src="{{ url_for('static', filename='live-coding-agent-viewer.js') }}" defer></script> | |
| {% endif %} | |
| <!-- Format Display Styles (document, code, etc.) --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/format-displays.css') }}"> | |
| <!-- Document Bounding Box Styles --> | |
| {% if frontend_assets.document_bbox | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='document-bbox.css') }}"> | |
| {% endif %} | |
| <!-- PDF Annotation Styles --> | |
| {% if frontend_assets.pdf_bbox | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='pdf-annotation.css') }}"> | |
| {% endif %} | |
| <!-- Display Logic Styles (conditional schema branching) --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='display-logic.css') }}"> | |
| <!-- Coreference Chain Styles --> | |
| {% if frontend_assets.coreference | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/coreference.css') }}"> | |
| {% endif %} | |
| <!-- Segmentation Tool Styles --> | |
| {% if frontend_assets.segmentation_tools | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/segmentation.css') }}"> | |
| {% endif %} | |
| <!-- Conversation Tree Styles --> | |
| {% if frontend_assets.conversation_tree | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/conversation-tree.css') }}"> | |
| {% endif %} | |
| <!-- Video Tracking Styles --> | |
| {% if frontend_assets.tracking | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/tracking.css') }}"> | |
| {% endif %} | |
| <!-- Triage Styles --> | |
| {% if frontend_assets.triage | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/triage.css') }}"> | |
| {% endif %} | |
| <!-- Entity Linking Styles (knowledge base linking) --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/entity-linking.css') }}"> | |
| <!-- Tiered Annotation Styles (ELAN-style hierarchical annotation) --> | |
| {% if frontend_assets.tiered_annotation | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/tiered-annotation.css') }}"> | |
| {% endif %} | |
| {% if agent_proxy_enabled | default(false) %} | |
| <!-- Agent Chat Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/agent-chat.css') }}"> | |
| {% endif %} | |
| {% if chat_enabled | default(false) %} | |
| <!-- LLM Chat Sidebar Styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/llm-chat-sidebar.css') }}?v=1"> | |
| {% endif %} | |
| <!-- Universal Memos sidebar styles --> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/memos.css') }}?v=2"> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/search.css') }}?v=2"> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/codebook.css') }}?v=10"> | |
| <!-- jQuery (required for span annotation) --> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> | |
| <!-- Project-level base CSS (injected from base_css config option) --> | |
| {{ PROJECT_BASE_CSS | safe }} | |
| </head> | |
| <body> | |
| <!-- Header navigation bar --> | |
| <nav class="potato-navbar" role="navigation"> | |
| <div class="navbar-inner"> | |
| <div class="navbar-brand"> | |
| {% if header_logo_url %} | |
| <img class="header-logo" src="{{ header_logo_url }}" alt="Logo"> | |
| {% endif %} | |
| <span class="task-name">Agent Trajectory Evaluation</span> | |
| </div> | |
| <div class="navbar-center"> | |
| <div class="annotation-status-indicator" id="annotation-status"> | |
| <span class="status-badge {{ annotation_status }}"> | |
| {% if annotation_status == 'labeled' %} | |
| {{ ui_lang.labeled_badge }} | |
| {% elif annotation_status == 'in_progress' %} | |
| {{ ui_lang.in_progress_badge }} | |
| {% else %} | |
| {{ ui_lang.not_labeled_badge }} | |
| {% endif %} | |
| </span> | |
| </div> | |
| <div class="navbar-divider"></div> | |
| <div class="progress-section"> | |
| <span class="progress-label">{{ ui_lang.progress_label }}</span> | |
| <span class="fw-medium" id="progress-counter">{{finished}}/{{total_count}}</span> | |
| </div> | |
| {% if ibws_round_info %} | |
| <div class="navbar-divider"></div> | |
| <div class="ibws-round-banner" id="ibws-round-banner"> | |
| <span class="ibws-round-label">Round {{ ibws_round_info.current_round }}{% if ibws_round_info.max_rounds %} of {{ ibws_round_info.max_rounds }}{% endif %}</span> | |
| <span class="ibws-round-detail">{{ ibws_round_info.terminal_items }}/{{ ibws_round_info.total_items }} ranked</span> | |
| </div> | |
| {% endif %} | |
| <div class="navbar-divider"></div> | |
| {% if instance_index is defined %} | |
| <div class="instance-number-section"> | |
| <span class="instance-number-label">Instance</span> | |
| <span class="fw-medium" id="instance-number">#{{ instance_index|int + 1 }}</span> | |
| </div> | |
| {% endif %} | |
| {% if not jumping_to_id_disabled %} | |
| <div class="navbar-divider"></div> | |
| <div class="nav-controls"> | |
| <button type="button" class="nav-control-btn" id="jump-unannotated-prev-btn" | |
| onclick="jumpToUnannotatedPrev()" title="{{ ui_lang.jump_prev_unannotated }}"> | |
| <i class="fas fa-backward"></i> | |
| </button> | |
| <button type="button" class="nav-control-btn" id="jump-unannotated-btn" | |
| onclick="jumpToUnannotated()" title="{{ ui_lang.jump_next_unannotated }}"> | |
| <i class="fas fa-forward"></i> | |
| </button> | |
| </div> | |
| <div class="navbar-divider"></div> | |
| <div class="goto-section"> | |
| <input type="number" id="go_to" placeholder="#" | |
| value="" min="0" max="{{total_count}}" required | |
| onfocusin="user_input()" onfocusout="user_input_leave()"> | |
| <button type="button" class="goto-btn" id="go-to-btn">{{ ui_lang.go_button }}</button> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <div class="navbar-end"> | |
| {% if is_adjudicator %} | |
| <a href="/adjudicate" class="adjudicate-btn" title="Adjudication Mode"> | |
| <i class="fas fa-gavel"></i> {{ ui_lang.adjudicate }} | |
| </a> | |
| {% endif %} | |
| {% if annotation_codebook_url %} | |
| <a href="{{ annotation_codebook_url | replace('data_files/', '/media/') }}" | |
| class="codebook-btn" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| title="Open annotation codebook"> | |
| <i class="fas fa-book-open"></i> {{ ui_lang.codebook }} | |
| </a> | |
| {% endif %} | |
| <div class="user-pill"> | |
| <span class="username" id="username-display">{{username}}</span> | |
| <a href="/logout" class="logout-btn">{{ ui_lang.logout }}</a> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="container-fluid"> | |
| <!-- Main annotation area --> | |
| <div id="task_layout" class="shadcn-card mb-2 {% if has_image_annotation or has_video_annotation or has_audio_annotation %}media-annotation{% else %}text-annotation{% endif %}"> | |
| <div class="shadcn-card-content"> | |
| <!-- Loading state --> | |
| <div id="loading-state" class="text-center py-5"> | |
| <div class="loading-spinner mb-3"></div> | |
| <p>{{ ui_lang.loading }}</p> | |
| </div> | |
| <!-- Error state --> | |
| <div id="error-state" class="error-message" style="display: none;"> | |
| <h5><i class="fas fa-exclamation-triangle me-2"></i>{{ ui_lang.error_heading }}</h5> | |
| <p id="error-message-text"></p> | |
| <button id="error-retry-btn" class="shadcn-button shadcn-button-primary" onclick="loadCurrentInstance()"> | |
| <i class="fas fa-redo me-2"></i>{{ ui_lang.retry_button }} | |
| </button> | |
| <a id="error-done-link" href="/done" class="shadcn-button shadcn-button-primary" style="display: none;"> | |
| <i class="fas fa-check me-2"></i>Finish | |
| </a> | |
| </div> | |
| <!-- Main content area --> | |
| <div id="main-content" style="display: none;"> | |
| {% if annotation_instructions | default('') %} | |
| <details class="annotation-instructions-banner" open> | |
| <summary>{{ ui_lang.instructions_heading|default('Instructions') }}</summary> | |
| <div class="annotation-instructions-content"> | |
| {{ annotation_instructions | safe }} | |
| </div> | |
| </details> | |
| {% endif %} | |
| <!-- Instance display area --> | |
| <div class="mb-4"> | |
| {% if has_instance_display | default(false) %} | |
| <!-- New instance_display mode: explicit content display configuration --> | |
| {{ display_html | safe }} | |
| <!-- Hidden element for legacy JS compatibility --> | |
| <div id="instance-text" style="display: none;"> | |
| <div id="text-content" data-original-text="{{instance_plain_text | sanitize_html}}">{{instance | sanitize_html}}</div> | |
| </div> | |
| {% elif has_video_annotation | default(false) %} | |
| <h5 class="mb-3">{{ ui_lang.video_to_annotate|default('Video to Annotate:') }}</h5> | |
| <!-- For video annotation, keep the text-content element accessible for JS but visually hidden --> | |
| <div id="instance-text" class="instance-text-container" style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0);"> | |
| <div id="text-content" data-original-text="{{instance_plain_text | sanitize_html}}">{{instance | sanitize_html}}</div> | |
| </div> | |
| {% elif has_audio_annotation | default(false) %} | |
| <h5 class="mb-3">{{ ui_lang.audio_to_annotate|default('Audio to Annotate:') }}</h5> | |
| <!-- For audio annotation, keep the text-content element accessible for JS but visually hidden --> | |
| <div id="instance-text" class="instance-text-container" style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0);"> | |
| <div id="text-content" data-original-text="{{instance_plain_text | sanitize_html}}">{{instance | sanitize_html}}</div> | |
| </div> | |
| {% elif has_image_annotation | default(false) %} | |
| <!-- For image annotation, hide this section - the image is loaded directly into the annotation canvas --> | |
| <!-- Store the image URL in a hidden element for JavaScript to access --> | |
| <div id="instance-text" style="display: none;"> | |
| <div id="text-content" data-original-text="{{instance_plain_text | sanitize_html}}" data-image-url="{{instance | sanitize_html}}">{{instance | sanitize_html}}</div> | |
| </div> | |
| {% else %} | |
| {% if is_annotation_page %} | |
| {% if ui_lang.text_to_annotate|default('Text to Annotate:') %} | |
| <h5 class="instance-text-heading">{{ ui_lang.text_to_annotate|default('Text to Annotate:') }}</h5> | |
| {% endif %} | |
| <div id="instance-text" class="p-3 border rounded instance-text-container" style="background-color: var(--light-bg); position: relative;"> | |
| <div id="text-content" data-original-text="{{instance_plain_text | sanitize_html}}" style="position: relative; z-index: 1; pointer-events: auto;">{{instance | sanitize_html}} | |
| <div id="span-overlays" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 2;"> | |
| </div> | |
| </div> | |
| </div> | |
| {% else %} | |
| <div id="instance-text" style="display: none;"> | |
| <div id="text-content" data-original-text=""></div> | |
| </div> | |
| {% endif %} | |
| {% endif %} | |
| </div> | |
| <!-- Status messages --> | |
| <div id="status" class="status" style="display: none;"></div> | |
| <!-- Hidden input for instance_id --> | |
| <input type="hidden" id="instance_id" name="instance_id" value="{{instance_id}}"> | |
| <!-- Full instance record as JSON, consumed by schemas that | |
| bind to structured instance fields (process_reward, | |
| trajectory_eval) via document.querySelector('[data-instance-json]'). | |
| tojson escapes single quotes so the single-quoted | |
| attribute is safe even when content contains apostrophes. --> | |
| <div id="instance-json-data" style="display: none;" | |
| data-instance-json='{{ (instance_record if instance_record is defined and instance_record else {}) | tojson }}'></div> | |
| <!-- Signal-based triage flag (why this item was prioritized) --> | |
| {% if triage_info is defined and triage_info %} | |
| <div class="triage-flag" id="triage-flag" role="note" | |
| aria-label="Triage: this item was prioritized in the queue"> | |
| <span class="triage-flag-icon" aria-hidden="true">!</span> | |
| <span class="triage-flag-text"> | |
| Prioritized for review<span class="triage-flag-sep"> · </span><span class="triage-flag-reason">{{ triage_info.reason }}</span> | |
| </span> | |
| </div> | |
| {% endif %} | |
| <!-- LLM-judge inline suggestion (judge ↔ human alignment) --> | |
| {% if judge_prediction is defined and judge_prediction %} | |
| {% set jp = judge_prediction %} | |
| {% set jconf = (jp.confidence * 100)|int %} | |
| {% set jqual = 'Low confidence' if jp.confidence < 0.5 else ('Moderate confidence' if jp.confidence < 0.8 else 'High confidence') %} | |
| <div class="judge-suggestion" id="judge-suggestion" | |
| data-schema="{{ jp.schema }}" data-judge-label="{{ jp.label }}"> | |
| <div class="judge-suggestion-row"> | |
| <div class="judge-suggestion-main"> | |
| <span class="judge-badge-icon" aria-hidden="true">⚖</span> | |
| <span class="judge-suggestion-label"> | |
| Judge suggests | |
| <span class="judge-suggested-value">{{ jp.label }}</span> | |
| for <span class="judge-schema-name">{{ jp.schema }}</span> | |
| </span> | |
| <span class="judge-confidence">{{ jqual }} · {{ jconf }}%</span> | |
| </div> | |
| <button type="button" class="btn btn-secondary judge-accept-btn" | |
| id="judge-accept-btn" | |
| data-schema="{{ jp.schema }}" data-judge-label="{{ jp.label }}"> | |
| Accept | |
| </button> | |
| {% if jp.running and jp.running.n %} | |
| <span class="judge-running" | |
| title="Running agreement between you/your team and the judge on this task (Cohen's κ)"> | |
| {{ (jp.running.agreement_rate * 100)|int }}% agree (n={{ jp.running.n }}){% if jp.running.kappa is not none %} · κ {{ '%.2f'|format(jp.running.kappa) }}{% endif %} | |
| </span> | |
| {% endif %} | |
| </div> | |
| {% if jp.reasoning %} | |
| <details class="judge-reasoning"> | |
| <summary>Judge reasoning</summary> | |
| <p>{{ jp.reasoning }}</p> | |
| </details> | |
| {% endif %} | |
| </div> | |
| <script> | |
| (function() { | |
| var btn = document.getElementById('judge-accept-btn'); | |
| if (!btn) return; | |
| btn.addEventListener('click', function() { | |
| var schema = btn.getAttribute('data-schema'); | |
| var label = btn.getAttribute('data-judge-label'); | |
| // Check the matching radio/checkbox input for this schema. | |
| var inputs = document.querySelectorAll( | |
| 'input.annotation-input[schema="' + schema + '"]'); | |
| var matched = false; | |
| inputs.forEach(function(inp) { | |
| if (inp.value === label || inp.getAttribute('label_name') === label) { | |
| inp.checked = true; | |
| inp.dispatchEvent(new Event('change', { bubbles: true })); | |
| matched = true; | |
| } | |
| }); | |
| if (matched) { btn.textContent = 'Accepted'; btn.disabled = true; } | |
| }); | |
| })(); | |
| </script> | |
| {% endif %} | |
| <!-- Annotation forms --> | |
| <div id="annotation-forms"> | |
| <!-- CONFIG_HASH: a26ad7a42ddec00ebb17dd83674016ae_3964352617691f36c1da1545fcf44452 --> | |
| <!-- Generated annotation layout file --> | |
| <!-- This file was automatically generated based on the annotation schemes in your config --> | |
| <!-- You can customize this file to modify the layout of your annotation interface --> | |
| <!-- Changes to this file will be preserved across server restarts --> | |
| <div class="annotation_schema"> | |
| <form id="step_evaluation" class="annotation-form trajectory-eval-container" | |
| action="javascript:void(0)" | |
| data-annotation-id="0" | |
| data-annotation-type="trajectory_eval" | |
| data-schema-name="step_evaluation" | |
| data-steps-key="steps" | |
| data-step-text-key="action" | |
| data-grid-columns="1"> | |
| <fieldset schema="step_evaluation"> | |
| <legend class="traj-eval-title">Evaluate each agent step for correctness and errors</legend> | |
| <div class="traj-score" id="step_evaluation-score"> | |
| Score: <strong id="step_evaluation-score-value">100</strong> / 100 | |
| </div> | |
| <!-- Step cards rendered by JS from instance data --> | |
| <div class="traj-steps-container" id="step_evaluation-steps"></div> | |
| <input type="hidden" | |
| class="annotation-input trajectory-eval-data-input" | |
| id="step_evaluation_step_evaluation_hidden" | |
| name="step_evaluation:::step_evaluation" | |
| schema="step_evaluation" | |
| label_name="step_evaluation" | |
| validation="" | |
| value=""> | |
| </fieldset> | |
| </form> | |
| <script> | |
| (function() { | |
| var _trajState = {}; | |
| var SCHEMA = 'step_evaluation'; | |
| var CONFIG = {"steps_key": "steps", "step_text_key": "action", "correctness_options": ["correct", "incorrect", "partially_correct"], "error_types": [{"name": "reasoning", "subtypes": ["logical_error", "factual_error", "planning_error"]}, {"name": "execution", "subtypes": ["wrong_tool", "wrong_args", "api_error"]}, {"name": "safety", "subtypes": ["harmful_action", "data_leak", "scope_violation"]}], "severities": [{"name": "minor", "weight": -1}, {"name": "major", "weight": -5}, {"name": "critical", "weight": -10}], "show_score": true, "max_score": 100}; | |
| var TYPE_OPTIONS_HTML = "<option value=\"\">-- select error type --</option><optgroup label=\"reasoning\"><option value=\"reasoning::logical_error\">logical_error</option><option value=\"reasoning::factual_error\">factual_error</option><option value=\"reasoning::planning_error\">planning_error</option></optgroup><optgroup label=\"execution\"><option value=\"execution::wrong_tool\">wrong_tool</option><option value=\"execution::wrong_args\">wrong_args</option><option value=\"execution::api_error\">api_error</option></optgroup><optgroup label=\"safety\"><option value=\"safety::harmful_action\">harmful_action</option><option value=\"safety::data_leak\">data_leak</option><option value=\"safety::scope_violation\">scope_violation</option></optgroup>"; | |
| var SEVERITY_RADIOS_TEMPLATE = "\n <label class=\"traj-severity-option\">\n <input type=\"radio\" class=\"traj-severity-radio\"\n name=\"traj-severity-STEPIDX-step_evaluation\"\n value=\"minor\">\n <span>minor (-1)</span>\n </label>\n <label class=\"traj-severity-option\">\n <input type=\"radio\" class=\"traj-severity-radio\"\n name=\"traj-severity-STEPIDX-step_evaluation\"\n value=\"major\">\n <span>major (-5)</span>\n </label>\n <label class=\"traj-severity-option\">\n <input type=\"radio\" class=\"traj-severity-radio\"\n name=\"traj-severity-STEPIDX-step_evaluation\"\n value=\"critical\">\n <span>critical (-10)</span>\n </label>"; | |
| var CORRECTNESS_BTNS_TEMPLATE = "<button type=\"button\" class=\"traj-correctness-btn traj-correctness-correct\" data-value=\"correct\">Correct</button> <button type=\"button\" class=\"traj-correctness-btn traj-correctness-incorrect\" data-value=\"incorrect\">Incorrect</button> <button type=\"button\" class=\"traj-correctness-btn traj-correctness-partially_correct\" data-value=\"partially_correct\">Partially Correct</button> "; | |
| function getState() { | |
| if (!_trajState[SCHEMA]) { | |
| _trajState[SCHEMA] = { steps: [] }; | |
| } | |
| return _trajState[SCHEMA]; | |
| } | |
| /* ---- build step cards from instance data ---- */ | |
| function buildStepCards() { | |
| var container = document.getElementById(SCHEMA + '-steps'); | |
| if (!container) return; | |
| // Read steps from the instance data embedded in the page | |
| var steps = []; | |
| try { | |
| var instanceDataEl = document.querySelector('[data-instance-json]'); | |
| if (instanceDataEl) { | |
| var instanceData = JSON.parse(instanceDataEl.getAttribute('data-instance-json')); | |
| steps = instanceData[CONFIG.steps_key] || []; | |
| } | |
| } catch(e) {} | |
| // Fallback: try reading from the displayed text content | |
| if (!steps.length) { | |
| var textEl = document.getElementById('text-content') || document.getElementById('instance-text'); | |
| if (textEl) { | |
| try { | |
| var parsed = JSON.parse(textEl.textContent || textEl.innerText); | |
| if (parsed && parsed[CONFIG.steps_key]) { | |
| steps = parsed[CONFIG.steps_key]; | |
| } | |
| } catch(e2) {} | |
| } | |
| } | |
| if (!steps.length) { | |
| // Show waiting state instead of error — steps may arrive | |
| // dynamically via live agent SSE events | |
| container.innerHTML = '<div class="traj-no-steps traj-waiting-steps">' + | |
| 'Waiting for agent steps… ' + | |
| '<span style="font-size:0.85em;color:#999;">(steps will appear here as the agent runs)</span></div>'; | |
| return; | |
| } | |
| var state = getState(); | |
| if (!state.steps.length) { | |
| steps.forEach(function(_, i) { | |
| state.steps.push({ step_index: i, correctness: null }); | |
| }); | |
| } | |
| container.innerHTML = ''; | |
| steps.forEach(function(step, idx) { | |
| _appendStepCard(container, step, idx); | |
| }); | |
| attachStepHandlers(); | |
| } | |
| /* ---- render a single step card ---- */ | |
| function _buildStepCardHtml(step, idx) { | |
| var stepText = typeof step === 'string' | |
| ? step | |
| : (step[CONFIG.step_text_key] || step.action_type || JSON.stringify(step)); | |
| // Include the thought if available (VLM chain-of-thought) | |
| var thoughtHtml = ''; | |
| if (step.thought) { | |
| thoughtHtml = '<div class="traj-step-thought">' + | |
| '<span class="traj-thought-label">Thought:</span> ' + | |
| escapeHtml(step.thought) + '</div>'; | |
| } | |
| var corBtns = CORRECTNESS_BTNS_TEMPLATE.replace(/STEPIDX/g, idx); | |
| var sevRadios = SEVERITY_RADIOS_TEMPLATE.replace(/STEPIDX/g, idx); | |
| return '<div class="traj-step-card" data-step-index="' + idx + '">' + | |
| '<div class="traj-step-header">' + | |
| '<span class="traj-step-number">Step ' + (idx + 1) + '</span>' + | |
| '<span class="traj-step-status" id="' + SCHEMA + '-status-' + idx + '"></span>' + | |
| '</div>' + | |
| '<div class="traj-step-text">' + escapeHtml(stepText) + '</div>' + | |
| thoughtHtml + | |
| '<div class="traj-correctness-row">' + corBtns + '</div>' + | |
| '<div class="traj-error-details" id="' + SCHEMA + '-error-' + idx + '" style="display:none;">' + | |
| '<div class="traj-field"><label>Error Type:</label>' + | |
| '<select class="traj-error-type" data-step="' + idx + '">' + TYPE_OPTIONS_HTML + '</select>' + | |
| '</div>' + | |
| '<div class="traj-field"><label>Severity:</label>' + | |
| '<div class="traj-severity-group">' + sevRadios + '</div>' + | |
| '</div>' + | |
| '<div class="traj-field"><label>Rationale:</label>' + | |
| '<textarea class="traj-rationale" data-step="' + idx + '" rows="2" placeholder="Explain the error..."></textarea>' + | |
| '</div>' + | |
| '</div>' + | |
| '</div>'; | |
| } | |
| function _appendStepCard(container, step, idx) { | |
| var div = document.createElement('div'); | |
| div.innerHTML = _buildStepCardHtml(step, idx); | |
| var card = div.firstElementChild; | |
| container.appendChild(card); | |
| _attachSingleCardHandlers(card, idx); | |
| } | |
| /* ---- add a step dynamically (called by live agent SSE) ---- */ | |
| function addStep(stepData) { | |
| var container = document.getElementById(SCHEMA + '-steps'); | |
| if (!container) return; | |
| // Clear the "waiting" message if present | |
| var waiting = container.querySelector('.traj-waiting-steps'); | |
| if (waiting) waiting.remove(); | |
| var state = getState(); | |
| var idx = state.steps.length; | |
| state.steps.push({ step_index: idx, correctness: null }); | |
| _appendStepCard(container, stepData, idx); | |
| saveState(); | |
| } | |
| /* ---- event handlers ---- */ | |
| function _attachSingleCardHandlers(card, idx) { | |
| card.querySelectorAll('.traj-correctness-btn').forEach(function(btn) { | |
| btn.addEventListener('click', function() { | |
| var value = btn.getAttribute('data-value'); | |
| var state = getState(); | |
| if (state.steps[idx] && state.steps[idx].correctness === value) { | |
| state.steps[idx] = { step_index: idx, correctness: null }; | |
| } else { | |
| if (!state.steps[idx]) state.steps[idx] = { step_index: idx }; | |
| state.steps[idx].correctness = value; | |
| } | |
| card.querySelectorAll('.traj-correctness-btn').forEach(function(b) { | |
| b.classList.remove('selected'); | |
| }); | |
| if (state.steps[idx].correctness === value) { | |
| btn.classList.add('selected'); | |
| } | |
| var errorDiv = document.getElementById(SCHEMA + '-error-' + idx); | |
| if (errorDiv) { | |
| errorDiv.style.display = (value === 'correct' || !state.steps[idx].correctness) ? 'none' : 'block'; | |
| } | |
| updateStepStatus(idx); | |
| saveState(); | |
| }); | |
| }); | |
| card.querySelectorAll('.traj-error-type').forEach(function(sel) { | |
| sel.addEventListener('change', function() { | |
| var state = getState(); | |
| if (!state.steps[idx]) return; | |
| var parts = sel.value.split('::'); | |
| state.steps[idx].error_type = parts[0] || ''; | |
| state.steps[idx].error_subtype = parts[1] || ''; | |
| saveState(); | |
| }); | |
| }); | |
| card.querySelectorAll('.traj-severity-group input[type="radio"]').forEach(function(radio) { | |
| radio.addEventListener('change', function() { | |
| var state = getState(); | |
| if (!state.steps[idx]) return; | |
| state.steps[idx].severity = radio.value; | |
| updateScore(); | |
| saveState(); | |
| }); | |
| }); | |
| card.querySelectorAll('.traj-rationale').forEach(function(ta) { | |
| ta.addEventListener('input', function() { | |
| var state = getState(); | |
| if (!state.steps[idx]) return; | |
| state.steps[idx].rationale = ta.value; | |
| saveState(); | |
| }); | |
| }); | |
| } | |
| function attachStepHandlers() { | |
| var container = document.getElementById(SCHEMA + '-steps'); | |
| if (!container) return; | |
| container.querySelectorAll('.traj-step-card').forEach(function(card) { | |
| var idx = parseInt(card.getAttribute('data-step-index'), 10); | |
| _attachSingleCardHandlers(card, idx); | |
| }); | |
| } | |
| function updateStepStatus(idx) { | |
| var statusEl = document.getElementById(SCHEMA + '-status-' + idx); | |
| if (!statusEl) return; | |
| var state = getState(); | |
| var s = state.steps[idx]; | |
| if (!s || !s.correctness) { | |
| statusEl.textContent = ''; | |
| statusEl.className = 'traj-step-status'; | |
| } else { | |
| statusEl.textContent = s.correctness.replace('_', ' '); | |
| statusEl.className = 'traj-step-status traj-status-' + s.correctness; | |
| } | |
| } | |
| function updateScore() { | |
| if (!CONFIG.show_score) return; | |
| var scoreEl = document.getElementById(SCHEMA + '-score-value'); | |
| if (!scoreEl) return; | |
| var state = getState(); | |
| var penalty = 0; | |
| state.steps.forEach(function(s) { | |
| if (s.severity) { | |
| var sev = CONFIG.severities.find(function(sv) { return sv.name === s.severity; }); | |
| if (sev) penalty += Math.abs(sev.weight); | |
| } | |
| }); | |
| scoreEl.textContent = Math.max(0, CONFIG.max_score - penalty); | |
| } | |
| /* ---- persistence ---- */ | |
| function saveState() { | |
| var state = getState(); | |
| var score = CONFIG.max_score; | |
| state.steps.forEach(function(s) { | |
| if (s.severity) { | |
| var sev = CONFIG.severities.find(function(sv) { return sv.name === s.severity; }); | |
| if (sev) score -= Math.abs(sev.weight); | |
| } | |
| }); | |
| score = Math.max(0, score); | |
| var data = JSON.stringify({ steps: state.steps, score: score }); | |
| var input = document.getElementById(SCHEMA).querySelector('.trajectory-eval-data-input'); | |
| if (input) { | |
| input.value = data; | |
| input.setAttribute('data-modified', 'true'); | |
| input.dispatchEvent(new Event('change', { bubbles: true })); | |
| } | |
| } | |
| function restoreFromHiddenInput() { | |
| var input = document.getElementById(SCHEMA).querySelector('.trajectory-eval-data-input'); | |
| if (!input) return false; | |
| var val = input.getAttribute('value') || input.value; | |
| if (!val) return false; | |
| try { | |
| var data = JSON.parse(val); | |
| if (data && data.steps && data.steps.length) { | |
| var state = getState(); | |
| state.steps = data.steps; | |
| return true; | |
| } | |
| } catch(e) {} | |
| return false; | |
| } | |
| function restoreVisualState() { | |
| var state = getState(); | |
| var container = document.getElementById(SCHEMA + '-steps'); | |
| if (!container) return; | |
| state.steps.forEach(function(s, idx) { | |
| var card = container.querySelector('[data-step-index="' + idx + '"]'); | |
| if (!card) return; | |
| // Restore correctness button | |
| if (s.correctness) { | |
| card.querySelectorAll('.traj-correctness-btn').forEach(function(btn) { | |
| if (btn.getAttribute('data-value') === s.correctness) { | |
| btn.classList.add('selected'); | |
| } | |
| }); | |
| // Show error details if not correct | |
| var errorDiv = document.getElementById(SCHEMA + '-error-' + idx); | |
| if (errorDiv && s.correctness !== 'correct') { | |
| errorDiv.style.display = 'block'; | |
| } | |
| } | |
| // Restore error type | |
| if (s.error_type) { | |
| var typeVal = s.error_subtype ? s.error_type + '::' + s.error_subtype : s.error_type; | |
| var sel = card.querySelector('.traj-error-type'); | |
| if (sel) sel.value = typeVal; | |
| } | |
| // Restore severity | |
| if (s.severity) { | |
| var radio = card.querySelector('input[type="radio"][value="' + s.severity + '"]'); | |
| if (radio) radio.checked = true; | |
| } | |
| // Restore rationale | |
| if (s.rationale) { | |
| var ta = card.querySelector('.traj-rationale'); | |
| if (ta) ta.value = s.rationale; | |
| } | |
| updateStepStatus(idx); | |
| }); | |
| updateScore(); | |
| } | |
| function escapeHtml(str) { | |
| var div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| /* ---- initialization ---- */ | |
| // CRITICAL: Check hidden input for server-restored value BEFORE | |
| // building default state (prevents IIFE overwrite bug). | |
| var hadServerData = restoreFromHiddenInput(); | |
| // Build step cards — uses a MutationObserver to wait for instance data | |
| // to appear if it hasn't yet (async loading). | |
| function tryBuildCards() { | |
| buildStepCards(); | |
| if (hadServerData) { | |
| restoreVisualState(); | |
| } | |
| } | |
| if (document.readyState === 'complete') { | |
| tryBuildCards(); | |
| } else { | |
| document.addEventListener('DOMContentLoaded', tryBuildCards); | |
| } | |
| // Expose for annotation.js restore pipeline | |
| window._trajState = _trajState; | |
| window._trajGetState = getState; | |
| window._trajBuildStepCards = buildStepCards; | |
| window._trajRestoreVisualState = restoreVisualState; | |
| window._trajSaveState = saveState; | |
| window._trajAddStep = addStep; | |
| // Listen for live agent step events so trajectory cards appear | |
| // as the agent runs (dispatched by live-agent-viewer.js SSE handler) | |
| document.addEventListener('live-agent-step', function(e) { | |
| if (e.detail) addStep(e.detail); | |
| }); | |
| })(); | |
| </script> | |
| </div> | |
| </div> | |
| <!-- Structured data elements (var_elems) --> | |
| {{ var_elems | safe }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Navigation buttons --> | |
| <div class="potato-nav"> | |
| {% if can_go_back | default(true) %} | |
| <button class="shadcn-button shadcn-button-outline" id="prev-btn" onclick="navigateToPrevious()"> | |
| <i class="fas fa-arrow-left me-2"></i>{{ ui_lang.previous_button }} | |
| </button> | |
| {% else %} | |
| <div id="prev-btn-placeholder"></div> | |
| {% endif %} | |
| <button class="shadcn-button shadcn-button-primary" id="next-btn" onclick="navigateToNext()"> | |
| {{ ui_lang.next_button }}<i class="fas fa-arrow-right ms-2"></i> | |
| </button> | |
| </div> | |
| {% if custom_footer_html | default('') %} | |
| <!-- Custom footer (e.g., HF Space promotional banner) --> | |
| {{ custom_footer_html | safe }} | |
| {% endif %} | |
| <!-- Footer --> | |
| <footer class="potato-footer"> | |
| {{ ui_lang.powered_by|default('Powered by') }} <a href="https://potatoannotator.com/" target="_blank" rel="noopener noreferrer">Potato</a> | |
| <span class="footer-sep">·</span> | |
| <a href="https://github.com/davidjurgens/potato" target="_blank" rel="noopener noreferrer">GitHub</a> | |
| <span class="footer-sep">·</span> | |
| <a href="https://github.com/davidjurgens/potato#cite-us" target="_blank" rel="noopener noreferrer">{{ ui_lang.cite_us|default('Cite Us') }}</a> | |
| </footer> | |
| </div> | |
| <!-- Bootstrap JS --> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |
| <!-- Configuration from server --> | |
| <script type="text/javascript"> | |
| window.config = { | |
| annotation_task_name: {{ annotation_task_name | tojson }}, | |
| is_annotation_page: {{ 'true' if is_annotation_page else 'false' }}, | |
| debug: {{ 'true' if debug_mode else 'false' }}, | |
| ui_debug: {{ 'true' if ui_debug else 'false' }}, | |
| server_debug: {{ 'true' if server_debug else 'false' }}, | |
| debug_phase: {{ debug_phase | tojson if debug_phase else 'null' }}, | |
| api_key: "test_api_key", | |
| username: {{ username | tojson }}, | |
| url_prefix: {{ url_prefix | default('') | tojson }}, | |
| ui_config: {} | |
| }; | |
| window.potatoUrl = function(path) { | |
| var prefix = (window.config && window.config.url_prefix) || ''; | |
| if (!prefix || typeof path !== 'string' || path.charAt(0) !== '/') { | |
| return path; | |
| } | |
| // Leave protocol-relative URLs ("//host/...") untouched — they are | |
| // absolute to another origin, not app-root-relative. | |
| if (path.charAt(1) === '/') { | |
| return path; | |
| } | |
| if (path.indexOf(prefix + '/') === 0 || path === prefix) { | |
| return path; | |
| } | |
| return prefix + path; | |
| }; | |
| (function() { | |
| var prefix = (window.config && window.config.url_prefix) || ''; | |
| if (!prefix) return; | |
| var originalFetch = window.fetch; | |
| if (originalFetch) { | |
| window.fetch = function(input, init) { | |
| if (typeof input === 'string') { | |
| input = window.potatoUrl(input); | |
| } else if (input && typeof input.url === 'string') { | |
| var prefixedUrl = window.potatoUrl(input.url); | |
| if (prefixedUrl !== input.url) { | |
| input = new Request(prefixedUrl, input); | |
| } | |
| } | |
| return originalFetch.call(this, input, init); | |
| }; | |
| } | |
| var originalSendBeacon = navigator.sendBeacon; | |
| if (originalSendBeacon) { | |
| navigator.sendBeacon = function(url, data) { | |
| return originalSendBeacon.call(this, window.potatoUrl(url), data); | |
| }; | |
| } | |
| // Server-Sent Events (live agent / live coding viewers) open | |
| // root-relative stream URLs that fetch/sendBeacon patching misses. | |
| // Wrap the constructor so new EventSource('/api/.../stream') resolves | |
| // under the mounted prefix. Note: SSE behind a path-prefix proxy also | |
| // needs `proxy_buffering off` on the stream location (see docs). | |
| var OriginalEventSource = window.EventSource; | |
| if (OriginalEventSource) { | |
| window.EventSource = function(url, config) { | |
| return new OriginalEventSource(window.potatoUrl(url), config); | |
| }; | |
| // Preserve instanceof checks and inherited members. | |
| window.EventSource.prototype = OriginalEventSource.prototype; | |
| ['CONNECTING', 'OPEN', 'CLOSED'].forEach(function(key) { | |
| window.EventSource[key] = OriginalEventSource[key]; | |
| }); | |
| } | |
| function prefixAttribute(root, selector, attribute) { | |
| var elements = []; | |
| if (root.matches && root.matches(selector)) { | |
| elements.push(root); | |
| } | |
| root.querySelectorAll(selector).forEach(function(element) { | |
| elements.push(element); | |
| }); | |
| elements.forEach(function(element) { | |
| var value = element.getAttribute(attribute); | |
| if (value && value.charAt(0) === '/') { | |
| element.setAttribute(attribute, window.potatoUrl(value)); | |
| } | |
| }); | |
| } | |
| function rewriteRootRelativeUrls(root) { | |
| root = root || document; | |
| prefixAttribute(root, 'a[href^="/"]', 'href'); | |
| prefixAttribute(root, 'form[action^="/"]', 'action'); | |
| prefixAttribute(root, 'img[src^="/"]', 'src'); | |
| prefixAttribute(root, 'video[src^="/"]', 'src'); | |
| prefixAttribute(root, 'audio[src^="/"]', 'src'); | |
| prefixAttribute(root, 'source[src^="/"]', 'src'); | |
| } | |
| document.addEventListener('DOMContentLoaded', function() { | |
| rewriteRootRelativeUrls(document); | |
| if (window.MutationObserver) { | |
| new MutationObserver(function(mutations) { | |
| mutations.forEach(function(mutation) { | |
| mutation.addedNodes.forEach(function(node) { | |
| if (node.nodeType === 1) { | |
| rewriteRootRelativeUrls(node); | |
| } | |
| }); | |
| }); | |
| }).observe(document.documentElement, {childList: true, subtree: true}); | |
| } | |
| }); | |
| })(); | |
| // Conditionally enable/disable console logging based on ui_debug setting | |
| if (!window.config.ui_debug) { | |
| // Store original console methods | |
| window._originalConsole = { | |
| log: console.log, | |
| debug: console.debug, | |
| info: console.info, | |
| warn: console.warn | |
| }; | |
| // Override with no-ops (keep error for critical issues) | |
| console.log = function() {}; | |
| console.debug = function() {}; | |
| console.info = function() {}; | |
| // Keep warn for important warnings | |
| } | |
| // Helper function to enable UI logging at runtime (for debugging in browser console) | |
| window.enableUIDebug = function() { | |
| if (window._originalConsole) { | |
| console.log = window._originalConsole.log; | |
| console.debug = window._originalConsole.debug; | |
| console.info = window._originalConsole.info; | |
| console.warn = window._originalConsole.warn; | |
| console.log('UI debug logging enabled'); | |
| } | |
| }; | |
| </script> | |
| <script id="ui-config" type="application/json"></script> | |
| <script> | |
| (function(){ | |
| try { | |
| var el = document.getElementById('ui-config'); | |
| if (el && el.textContent && el.textContent.trim().length > 0) { | |
| window.config.ui_config = JSON.parse(el.textContent); | |
| } | |
| } catch (e) { console.warn('Failed to parse ui_config', e); } | |
| })(); | |
| </script> | |
| <!-- Load annotation JavaScript --> | |
| <script src="{{ url_for('static', filename='interaction_tracker.js') }}?v=1"></script> | |
| <script src="{{ url_for('static', filename='span-core.js') }}?v=11"></script> | |
| <script src="{{ url_for('static', filename='ai_assistant_manager.js') }}?v=1"></script> | |
| <!-- Display Logic Manager (conditional schema branching) - must load before annotation.js --> | |
| <script src="{{ url_for('static', filename='display-logic.js') }}?v=1"></script> | |
| <!-- Instance display manager (for new instance_display configuration) --> | |
| {% if has_instance_display | default(false) %} | |
| <script src="{{ url_for('static', filename='instance-display.js') }}?v=2"></script> | |
| {% endif %} | |
| <script src="{{ url_for('static', filename='annotation.js') }}?v=23"></script> | |
| <!-- Fabric.js for image annotation (loaded from CDN) --> | |
| {% if frontend_assets.image_annotation | default(false) %} | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script> | |
| <!-- Image annotation manager --> | |
| <script src="{{ url_for('static', filename='image-annotation.js') }}?v=2"></script> | |
| {% endif %} | |
| <!-- Visual AI Assistant for image/video annotation (loaded conditionally) --> | |
| {% if ai_enabled and ((frontend_assets.image_annotation | default(false)) or (frontend_assets.video_annotation | default(false))) %} | |
| <script src="{{ url_for('static', filename='visual_ai_assistant.js') }}?v=1"></script> | |
| <!-- Option Highlight Manager for dimming less-likely options --> | |
| <script src="{{ url_for('static', filename='option_highlight_manager.js') }}?v=2"></script> | |
| {% endif %} | |
| <!-- Peaks.js for audio/video annotation (stored locally for reliability) --> | |
| {% if (frontend_assets.audio_annotation | default(false)) or (frontend_assets.video_annotation | default(false)) %} | |
| <script src="{{ url_for('static', filename='peaks.min.js') }}"></script> | |
| {% endif %} | |
| <!-- Audio annotation manager --> | |
| {% if frontend_assets.audio_annotation | default(false) %} | |
| <script src="{{ url_for('static', filename='audio-annotation.js') }}?v=2"></script> | |
| {% endif %} | |
| <!-- Video annotation manager --> | |
| {% if frontend_assets.video_annotation | default(false) %} | |
| <script src="{{ url_for('static', filename='video-annotation.js') }}?v=10"></script> | |
| {% endif %} | |
| <!-- Span link manager --> | |
| {% if frontend_assets.span_link | default(false) %} | |
| <script src="{{ url_for('static', filename='span-link-manager.js') }}?v=7"></script> | |
| {% endif %} | |
| <!-- Event annotation manager --> | |
| {% if frontend_assets.event_annotation | default(false) %} | |
| <script src="{{ url_for('static', filename='event-annotation.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Entity linking for knowledge bases (Wikidata, UMLS) --> | |
| <script src="{{ url_for('static', filename='entity-linking.js') }}?v=1"></script> | |
| <!-- Coreference chain manager --> | |
| {% if frontend_assets.coreference | default(false) %} | |
| <script src="{{ url_for('static', filename='coreference-manager.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Segmentation tools --> | |
| {% if frontend_assets.segmentation_tools | default(false) %} | |
| <script src="{{ url_for('static', filename='segmentation-tools.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Conversation tree interaction --> | |
| {% if frontend_assets.conversation_tree | default(false) %} | |
| <script src="{{ url_for('static', filename='conversation-tree.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Video tracking interpolation and UI --> | |
| {% if frontend_assets.tracking | default(false) %} | |
| <script src="{{ url_for('static', filename='tracking-interpolation.js') }}?v=1"></script> | |
| <script src="{{ url_for('static', filename='tracking-ui.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Triage annotation manager --> | |
| {% if frontend_assets.triage | default(false) %} | |
| <script src="{{ url_for('static', filename='triage.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Tiered annotation manager (ELAN-style hierarchical annotation) --> | |
| {% if frontend_assets.tiered_annotation | default(false) %} | |
| <script src="{{ url_for('static', filename='tiered-annotation.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Document bounding box annotation --> | |
| {% if frontend_assets.document_bbox | default(false) %} | |
| <script src="{{ url_for('static', filename='document-bbox.js') }}?v=3"></script> | |
| {% endif %} | |
| <!-- PDF bounding box annotation --> | |
| {% if frontend_assets.pdf_bbox | default(false) %} | |
| <script src="{{ url_for('static', filename='pdf-bbox.js') }}?v=3"></script> | |
| {% endif %} | |
| {% if agent_proxy_enabled | default(false) %} | |
| <!-- Agent Chat UI --> | |
| <script src="{{ url_for('static', filename='agent-chat.js') }}?v=1"></script> | |
| {% endif %} | |
| {% if chat_enabled | default(false) %} | |
| <!-- LLM Chat Sidebar --> | |
| <script src="{{ url_for('static', filename='llm-chat-sidebar.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Web Agent Trace Viewer (overlays + step navigation) --> | |
| {% if frontend_assets.web_agent_viewer | default(false) %} | |
| <script src="{{ url_for('static', filename='web-agent-overlays.js') }}?v=1"></script> | |
| <script src="{{ url_for('static', filename='web-agent-viewer.js') }}?v=1"></script> | |
| {% endif %} | |
| {% if frontend_assets.web_agent_playback | default(false) %} | |
| <script src="{{ url_for('static', filename='web-agent-playback.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Web Agent Interaction Recorder (creation mode) --> | |
| {% if frontend_assets.web_agent_recorder | default(false) %} | |
| <script src="{{ url_for('static', filename='web-agent-recorder.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Live Agent Viewer (real-time agent interaction) --> | |
| {% if live_agent_enabled | default(false) %} | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/live-agent.css') }}?v=1"> | |
| <script src="{{ url_for('static', filename='live-agent-viewer.js') }}?v=1"></script> | |
| {% endif %} | |
| <!-- Global keyboard debug (temporary) --> | |
| <script> | |
| window.addEventListener('keydown', function(e) { | |
| console.log('[GLOBAL DEBUG] keydown:', e.key, 'code:', e.code, 'target:', e.target.tagName); | |
| }, true); | |
| </script> | |
| <!-- Instance height limit functionality --> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Apply maximum instance height if configured | |
| if (window.config && window.config.ui_config && window.config.ui_config.max_instance_height) { | |
| const maxHeight = window.config.ui_config.max_instance_height; | |
| const instanceTextContainer = document.getElementById('instance-text'); | |
| if (instanceTextContainer) { | |
| // Set CSS custom property for max height | |
| instanceTextContainer.style.setProperty('--max-instance-height', maxHeight + 'px'); | |
| // Add the max-height-limited class to enable scrolling | |
| instanceTextContainer.classList.add('max-height-limited'); | |
| console.log('Applied maximum instance height:', maxHeight + 'px'); | |
| } | |
| } | |
| }); | |
| </script> | |
| <!-- Universal Memos sidebar (self-hides if annotation_ui.memos is off: | |
| the API returns 503 and the toggle stays hidden) --> | |
| {% if is_annotation_page %} | |
| <button type="button" id="memo-panel-toggle" class="memo-panel-toggle" hidden> | |
| 📝 Notes | |
| </button> | |
| <div id="memo-panel" class="memo-panel" hidden aria-label="Notes panel"> | |
| <div class="memo-panel-header"> | |
| <span>Notes</span> | |
| <button type="button" id="memo-panel-close" aria-label="Close notes">×</button> | |
| </div> | |
| <div id="memo-list" class="memo-list"></div> | |
| <div class="memo-composer"> | |
| <textarea id="memo-new-body" placeholder="Add a note about this instance…"></textarea> | |
| <div id="memo-anchor-wrap" class="memo-anchor-hint" hidden> | |
| <label> | |
| <input type="checkbox" id="memo-anchor-check"> | |
| Attach to selected text: “<span id="memo-anchor-quote"></span>” | |
| </label> | |
| </div> | |
| <div class="memo-composer-row"> | |
| <label>Visibility | |
| <select id="memo-new-visibility"> | |
| <option value="private">Private</option> | |
| <option value="shared">Shared</option> | |
| </select> | |
| </label> | |
| <button type="button" id="memo-add-btn" class="memo-primary">Add note</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="{{ url_for('static', filename='memos.js') }}?v=1"></script> | |
| <!-- Annotator search-and-claim sidebar (self-hides unless | |
| search.annotator_claim is enabled: the enable-probe only gets | |
| 400 when the feature is on and the user is authed). --> | |
| <button type="button" id="search-panel-toggle" class="search-panel-toggle" hidden> | |
| 🔍 Find | |
| </button> | |
| <div id="search-panel" class="search-panel" hidden aria-label="Search panel"> | |
| <div class="search-panel-header"> | |
| <span>Find instances</span> | |
| <button type="button" id="search-panel-close" aria-label="Close search">×</button> | |
| </div> | |
| <div class="search-form"> | |
| <input type="text" id="search-q" placeholder="Search instance text…" | |
| aria-label="Search query"> | |
| <button type="button" id="search-go" class="search-primary">Search</button> | |
| </div> | |
| <div id="search-results" class="search-results" aria-live="polite"> | |
| <div class="search-empty">Search to find instances, then Claim | |
| one to add it to your queue.</div> | |
| </div> | |
| </div> | |
| <script src="{{ url_for('static', filename='search.js') }}?v=3"></script> | |
| <!-- Universal Codebook tray (self-hides unless the codebook is | |
| enabled: the enable-probe is not 200 otherwise). The composer | |
| only appears when codebook_mode is extensible/open. --> | |
| <button type="button" id="cb-panel-toggle" class="cb-panel-toggle" hidden> | |
| 🏷 Codebook | |
| </button> | |
| <div id="cb-panel" class="cb-panel" hidden role="region" | |
| aria-label="Codebook panel"> | |
| <div class="cb-panel-header"> | |
| <span>Codebook</span> | |
| <button type="button" id="cb-panel-close" | |
| aria-label="Close codebook">×</button> | |
| </div> | |
| <div id="cb-stale-banner" class="cb-stale-banner" hidden> | |
| <span class="cb-stale-msg" id="cb-stale-msg" | |
| role="status"></span> | |
| <button type="button" id="cb-stale-dismiss" | |
| class="cb-stale-x" | |
| aria-label="Dismiss this notice">×</button> | |
| </div> | |
| <div id="cb-tree" class="cb-tree" aria-live="polite"> | |
| <div class="cb-empty">Loading…</div> | |
| </div> | |
| <div class="cb-worklist-section" id="cb-worklist-section" hidden> | |
| <h2 id="cb-worklist-head" class="cb-worklist-head">Review</h2> | |
| <div id="cb-worklist" class="cb-worklist" | |
| aria-labelledby="cb-worklist-head"> | |
| <div class="cb-empty">Nothing to review.</div> | |
| </div> | |
| </div> | |
| <div id="cb-admin-section" class="cb-admin-section" hidden> | |
| <h2 class="cb-admin-head">Curate (admin)</h2> | |
| <div class="cb-admin-group"> | |
| <label class="cb-admin-label" | |
| for="cb-merge-src">Merge a code into another</label> | |
| <div class="cb-admin-row"> | |
| <select id="cb-merge-src" class="cb-admin-select" | |
| aria-label="Code to merge from"></select> | |
| <span class="cb-admin-arrow" aria-hidden="true">→</span> | |
| <select id="cb-merge-dst" class="cb-admin-select" | |
| aria-label="Code to merge into"></select> | |
| </div> | |
| <button type="button" id="cb-merge-btn" | |
| class="cb-primary">Merge</button> | |
| </div> | |
| <div class="cb-admin-group"> | |
| <label class="cb-admin-label" | |
| for="cb-split-src">Split a code by annotator</label> | |
| <div class="cb-admin-row"> | |
| <select id="cb-split-src" class="cb-admin-select" | |
| aria-label="Code to split"></select> | |
| <input type="text" id="cb-split-annotator" | |
| class="cb-admin-input" | |
| placeholder="annotator" | |
| aria-label="Annotator to split out"> | |
| </div> | |
| <input type="text" id="cb-split-name" | |
| class="cb-admin-input" | |
| placeholder="New code name…" | |
| aria-label="New code name"> | |
| <button type="button" id="cb-split-btn" | |
| class="cb-primary">Split</button> | |
| </div> | |
| <div id="cb-admin-error" class="cb-error" hidden | |
| role="alert" tabindex="-1"></div> | |
| <div id="cb-admin-status" class="cb-admin-status" | |
| role="status" aria-live="polite"></div> | |
| <div class="cb-admin-group"> | |
| <h3 class="cb-admin-sub" id="cb-proposals-head"> | |
| Pending proposals</h3> | |
| <div id="cb-proposals" class="cb-proposals" | |
| aria-labelledby="cb-proposals-head" | |
| aria-live="polite"> | |
| <div class="cb-empty">No pending proposals.</div> | |
| </div> | |
| </div> | |
| <details class="cb-admin-group cb-changes-wrap"> | |
| <summary class="cb-admin-sub">Recent changes</summary> | |
| <ul id="cb-changes" class="cb-changes-list"></ul> | |
| </details> | |
| </div> | |
| <div id="cb-composer" class="cb-composer" hidden> | |
| <div class="cb-composer-row"> | |
| <input type="text" id="cb-new-name" | |
| placeholder="Add a code…" | |
| aria-label="New code name"> | |
| <button type="button" id="cb-add-btn" | |
| class="cb-primary">Add</button> | |
| </div> | |
| <div id="cb-error" class="cb-error" hidden></div> | |
| <div id="cb-mode-hint" class="cb-mode-hint"></div> | |
| </div> | |
| </div> | |
| <script src="{{ url_for('static', filename='codebook.js') }}?v=10"></script> | |
| {% endif %} | |
| </body> | |
| </html> | |