Custom Interfaces

Prodigy ships with a range of built-in annotation interfaces for annotating text, images and other content. You can also put together fully custom solutions by combining interfaces and adding custom HTML, CSS and JavaScript.

Combining interfaces with blocks New: 1.9

Blocks are a new and exciting feature that let you freely combine different annotation interfaces. For example, you can add a text_input field to the ner_manual interface to collect additional free-form comments from the annotators. Or you can add custom html blocks before and after the image_manual UI or a multiple choice interface. Here’s an example:

This live demo requires JavaScript to be enabled.

View recipe example

Blocks are defined as a list of dictionaries in the "config" returned by your recipe and will be rendered in order. They all use the data present in the current annotation task, unless you override certain properties in the block. Each block needs to define at least a "view_id", mapping to one of the existing annotation interfaces.

recipe.py (excerpt)return {
    "dataset": dataset,
    "stream": stream,
    "view_id": "blocks",
    "config": {
        "blocks": [
            {"view_id": "ner_manual"},
            {"view_id": "choice", "text": None},
            {"view_id": "text_input", "field_rows": 3},
        ]
    }
}

In the above example, the "text" property of the second block is overwritten and set to None, so it won’t render any text. That’s because both ner_manual and choice render the "text" of an annotation task if present, so it would show up twice, once in each block. You typically also want to define text_input settings with the block, so you can have multiple inputs if needed and don’t need to store the presentational settings with each annotation task.

Aside from the task content, you can also define the following config settings with each block:

html_templateHTML template with variables to render the task content. Setting it in the block allows you to have multiple custom HTML blocks rendering different content.
labelsLabel set used in the ner_manual and image_manual interfaces. Setting it in the block is a bit cleaner and technically allows you to create blocks using both interfaces together with different labels (although not necessarily recommended).

The annotation task, i.e. what your stream sends out, should contain the content you’re annotating and what you want to save in the database. The config and overrides in the blocks are applied to all tasks, so they should only really include presentational settings, like the HTML template to use, the ID or placeholder of input fields or overrides to prevent the same content from being displayed by several blocks.

Multiple blocks of the same type are only supported for the text_input and html interface. For text input blocks, you can use the "field_id" to assign unique names to your fields and make sure they all write the user input to different keys. For HTML blocks, you can use different "html_template" values that reference different keys of your task.

Example[
  {"view_id": "text_input", "field_id": "user_input_a", "field_rows": 1},
  {"view_id": "text_input", "field_id": "user_input_b", "field_rows": 3},
  {"view_id": "html", "html_template": "<strong>{{content_a}}</strong>"},
  {"view_id": "html", "html_template": "<em>{{content_b}}</em>"}
]

Custom interfaces with HTML, CSS and JavaScript

The html interface lets you render any plain HTML content, specified as the "html" key in the annotation task.

JSON task format{"html": "<img src='image.jpg'>"}

If you don’t want to include the full markup in every task, you can also specify a html_template in your global or the project’s prodigy.json config file. Prodigy uses Mustache to render the templates, and makes all task properties available as variables wrapped in double curly braces, e.g. {{text}}. Using HTML templates, you can visualize complex, nested data without having to convert your input to match Prodigy’s format. When you annotate the task, Prodigy will simply add an "answer" key.

For example, let’s say your annotation tasks look like this:

JSON task format{
  "title": "This is a title",
  "image": "https://images.unsplash.com/photo-1472148439583-1f4cf81b80e0?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=21dba460262331eb682c38680f7f1135",
  "data": {"value": "This is a value"},
  "meta": {
    "by": "Peter Nguyen",
    "url": "https://unsplash.com/@peterng1618"
  }
}

You could then write the following HTML template that has access to the task properties as template variables, including the nested data.value:

HTML template<h2>{{title}}</h2>
<strong>{{data.value}}</strong>
<br />
<img src="{{image}}" />
This live demo requires JavaScript to be enabled.

All theme settings will also be available to the custom interface via the {{theme}} variable. This lets you re-use Prodigy’s color and styles, without having to hard-code them into your template. For example:

<mark style="background: {{theme.bgHighlight}}">{{text}}</mark>

JavaScript and CSS New: 1.7

The "global_css" config setting lets you pass in a string of CSS overrides that will be added to the global scope of the app. This lets you customize styles beyond just the color theme. As of v1.7.0, the Prodigy app exposes the following human-readable class names:

Class nameDescription
.prodigy-rootRoot element the app is rendered into.
.prodigy-buttonsRow of action buttons (accept, reject, ignore, undo).
.prodigy-button-acceptNew: 1.9 The accept button.
.prodigy-button-rejectNew: 1.9 The reject button.
.prodigy-button-ignoreNew: 1.9 The ignore button.
.prodigy-button-undoNew: 1.9 The undo button.
.prodigy-annotatorNew: 1.9 Main container of the annotation UI (excluding the sidebar).
.prodigy-sidebar-wrapperNew: 1.9 Container of the sidebar.
.prodigy-sidebarNew: 1.9 The sidebar.
.prodigy-sidebar-titleNew: 1.9 The sidebar title bar.
.prodigy-containerContainer of the annotation card (including title, content, meta).
.prodigy-contentMain content of the annotation card (containing the text or image).
.prodigy-titleTitle bar containing the label(s).
.prodigy-title-wrapperSticky wrapper around the title bar containing the labels.
.prodigy-metaMeta information in the bottom right corner of the annotation card.
.prodigy-text-inputText input fields (<input> or <textarea>) created by the text_input interface.

In addition to the class names, the root element also exposes the following data attributes:

Data attributeDescription
data-prodigy-view-idThe name of the current interface, i.e. "ner_manual".
data-prodigy-recipeThe name of the current recipe, i.e. ner.manual or terms.teach.

This lets you implement custom styling specific to individual recipes or interfaces in the same global stylesheet. For example:

[data-prodigy-view-id='ner_manual'] .prodigy-title {
  /* Change background for the title/labels bar only in the manual NER interface */
  background: green;
}

[data-prodigy-recipe='custom-recipe'] .prodigy-buttons {
  /* Hide the action buttons only in a custom recipe "custom-recipe"  */
  display: none;
}

As of v1.7.0, the "javascript" config setting is available across all interfaces and lets you pass in a string of JavaScript code that will be executed in the global scope. This lets you implement fully custom interfaces that modify and interact with the current incoming task. The following internals are exposed via the global window.prodigy object:

PropertyTypeDescription
viewIdstringThe ID of the current interface, e.g. 'ner_manual'.
configobjectThe user configuration settings (e.g. prodigy.json plus recipe config).
contentobjectThe content of the current task (e.g. {'text': 'Some text'}).
themeobjectTheme variables like colors.
updatefunctionUpdate the current task. Takes an object with the updates and performs a shallow (!) merge.
answerfunctionAnswer the current task. Takes the string value of the answer, e.g. 'accept'.

Here’s a simple example of a custom HTML template and JavaScript function that allows toggling a button to change the task text from uppercase to lowercase, and vice versa. For more details and inspiration, check out this thread on the forum.

HTML Template<button class="custom-button" onClick="updateText()">
  👇 Text to uppercase
</button>
<br />
<strong>{{text}}</strong>
JavaScriptlet upper = false

function updateText() {
  const text = window.prodigy.content.text
  const newText = !upper ? text.toUpperCase() : text.toLowerCase()
  window.prodigy.update({ text: newText })
  upper = !upper
  document.querySelector('.custom-button').textContent =
    '👇 Text to ' + (upper ? 'lowercase' : 'uppercase')
}

You can also listen to the following custom events fired by the app:

EventDescription
prodigymountThe app has mounted.
prodigyupdateThe UI was updated.
prodigyanswerThe user answered a question.
prodigyundoNew: 1.8.4 The user hit undo and re-added a task to the queue.
prodigyspanselectedA span was selected in the manual NER interface.

Some events expose additional data via the details property. For example, the prodigyanswer event exposes the task that was answered, as well as the answer that was chosen: 'accept', 'reject' or 'ignore'.

JavaScriptdocument.addEventListener('prodigyanswer', event => {
  const { answer, task } = event.detail
  console.log('The answer was: ', answer)
})