{% extends "base.html" %} {% block title %}{{ t('interview.title') }} {{ conversation.id }} - {{ t('app.title') }}{% endblock %} {% block content %}

{{ t('interview.back') }}

{{ t('interview.title') }} {{ conversation.id }}

{# A figure only once something has actually been spent. Before the first turn the total is zero for every model, priced or not, and a zero over somebody's real bill is the answer providers.py refuses to give. #}

{{ t('interview.tokens', input=conversation.usage.input_tokens, output=conversation.usage.output_tokens) }}{% if spent is not none and (conversation.usage.input_tokens or conversation.usage.output_tokens) %}, {{ t('interview.spent', amount='%.4f'|format(spent)) }}{% endif %}

{% if not engine.ready %} {% include "_engine.html" %} {% endif %}
{% for moment in moments %} {% if moment.kind == 'said' %}

{{ t('interview.said') }}

{{ moment.text }}
{% elif moment.kind == 'asked' %}

{{ t('interview.asked') }}

{{ moment.text }}
{% elif moment.kind == 'call' %}
{{ t('interview.tool_call') }} {{ moment.name }} {{ moment.arguments | tojson }}
{% else %}
{% if moment.is_error %}{{ t('interview.tool_failed') }}{% else %}{{ t('interview.tool_result') }}{% endif %}
{{ moment.text }}
{% endif %} {% endfor %}
{% set sheet = conversation.sheet %} {# The sheet skeleton is on the page even before a sheet exists: the labels come from the language pack server side, and the script only ever fills values into slots and reveals the panel. Approval is a plain form POST, the one writer of `approved`. #}

{{ t('interview.sheet') }}

{% if notice == 'sheet-changed' %}

{{ t('interview.sheet_changed') }}

{% elif notice == 'first-line-missing' %}

{{ t('interview.sheet_first_line_missing') }}

{% elif notice == 'turn-running' %}

{{ t('interview.error_turn_running') }}

{% endif %}

{{ t('interview.sheet_hint') }}

{{ t('interview.sheet_angle') }}
{{ sheet.angle if sheet }}
{{ t('interview.sheet_elements') }}
    {% if sheet %}{% for element in sheet.elements %}
  • {{ element }}
  • {% endfor %}{% endif %}
{{ t('interview.sheet_moment') }}
{{ sheet.moment if sheet }}
{{ t('interview.sheet_conviction') }}
{{ sheet.conviction if sheet }}
{{ t('interview.sheet_first_lines') }}
{# The step F1 exists for. The skill already says the post is written for the chosen proposal, to the character; nothing recorded a choice, so nothing was ever written for one, and a model with no decision opens on a lukewarm self description. The radios carry `form`, so the choice lives beside the line it is about and still travels with the approval below. Taking neither is on the list: it is a decision, and the only thing refused here is deciding nothing. #}
    {% if sheet %}{% for line in sheet.first_lines %}
  • {% if sheet.state == 'proposed' and conversation.state == 'open' %}{% else %}{% if sheet.first_line == loop.index0 %}{{ t('interview.sheet_first_line_taken') }} {% endif %}{{ line }}{% endif %}
  • {% endfor %}{% if sheet.state == 'proposed' and conversation.state == 'open' %}
  • {% elif sheet.first_line == neither %}
  • {{ t('interview.sheet_first_line_none_taken') }}
  • {% endif %}{% endif %}
{# How it arrived. Hidden until there is something to say, and filled by the script from the same frame that fills the fields above: a sheet parsed out of free text is a weaker object than one a model committed to. #}

{{ t('interview.sheet_problems') }}

{{ t('interview.sheet_problems_hint') }}

{% if conversation.state == 'open' %}
{# The identity of the sheet as this page shows it. The disk can be newer than the screen, and an approval only ever signs what was read. #}

{{ t('interview.sheet_approve_hint') }}

{% endif %}

{{ t('interview.sheet_approved_notice') }}

{% if conversation.state == 'open' and sheet and sheet.state == 'approved' %}

{{ t('interview.sheet_closed_notice') }}

{% elif conversation.state == 'open' %} {# Asking for the sheet is the person's call: they are the one who knows the interview has enough material. The turn behind this button requires the tool, which is what makes the mechanism fire on a model that would otherwise answer the sheet in prose. #} {# How much material is on the table. Beside the sentence the engine writes every turn, never instead of it: what is missing is a judgement about the material and only that sentence carries it. This says how much there is, and it shows its counts, so the percentage is a reading of something somebody can check rather than a verdict of its own. #}

{{ t('interview.sufficiency', **gauge) }} {{ t('interview.sufficiency_counts', **gauge) }}

{{ t('interview.sufficiency_hint') }}

{# Not before the first turn. A sheet asked for then is a sheet the model has to invent, which is the failure the sheet exists to catch, and the server refuses it: this is the same rule on the screen instead of behind a click. Hidden rather than absent, because the page does not reload between two interview turns and the client reveals it on the frame that says the first words reached disk. #}

{{ t('interview.sheet_ask_hint') }}

{{ t('interview.awaiting') }}

{# The accelerator is announced rather than left to be discovered. The button is what a keyboard and a screen reader reach first, and this page posts without JavaScript, so it stays. #} {{ t('interview.send_hint') }}
{% else %}

{{ t('interview.closed_notice', post=conversation.post) }}

{% endif %} {% if sheet and sheet.state == 'approved' %}

{{ t('interview.draft') }}

{# A click that arrived from a screen older than the disk. It belongs here rather than beside the sheet: it is about the post, and the sentence is the only thing telling somebody their post was not the one they read. #} {% if notice == 'draft-changed' %}

{{ t('interview.draft_changed') }}

{% endif %}

{{ t('interview.draft_hint') }}

{% if conversation.state == 'open' %} {% if trace %} {# The revision loop of the skill, made a box. What is typed here is kept and joins the Said side: the hint says so, because somebody has to know that a correction typed here is quotable and a request typed here is on the record. The button reads it; nothing is appended to the interview. #} {% if conversation.revisions %}

{{ t('interview.revision_history') }}

{% endif %} {# What the request is about. A whole post by default, which is what a revision always used to be, or one block of it. The block is echoed under the picker word for word: somebody has to see the text that will be replaced, not a summary of where it is. #} {% if blocks|length > 1 %}
{{ scope.text if scope }}
{% endif %}

{{ t('interview.revision_hint') }}

{# A placeholder that teaches by example rather than describing the field. Somebody who reads "your request" writes "make it better". #} {# Which interview this box belongs to. What is typed here outlives the page, in the browser's own store and per interview, so a request half written for one post never turns up under another. #} {% endif %} {# The scope, on the action line rather than in a paragraph above it. It is the sentence somebody reads with their hand already on the button. #}

{{ t('interview.revision_scope_line') }}

{# Where a drafting turn talks: here, beside the scope, the echo of the passage and the box, and not in the thread at the top of the page. The refusal contract of `linkedin-post` means a turn can answer with a question instead of a post, and an answer shown a screen away from its own question is answered by picking the passage again by hand. Empty until a turn speaks, and emptied by the next one. #} {% endif %} {% if not trace %}

{{ t('interview.draft_none') }}

{% else %}

{{ t('interview.draft_version', number=trace.version) }} {{ t('interview.draft_written', stamp=trace.written) }}{% if trace.restored %}, {{ t('interview.draft_restored', stamp=trace.restored) }}{% endif %}

{# Both only from the second version on, which is the rule the resume button and the passage picker already follow: an affordance with no history behind it is not on the screen, and a legend about a previous version is a legend about nothing. #} {% if trace.version > 1 %}

{{ t('interview.moved_legend') }}

{# The identity of the post as this page shows it. A turn can rewrite the draft behind a screen already drawn, and going back from that screen would throw away a version nobody read. #} {{ t('interview.draft_revert_hint') }}
{% endif %}
{# The post in the person's own typeface, with every claim no anchor backs marked in it. The marking is decided server side, in anchors.lines, so a reload and a live frame paint the same verdict. #}
{% for row in trace.lines %}{% if row %}{% for piece in row %}{% if piece.status == 'anchored' %}{{ piece.text }}{% else %}{{ piece.text }}{% endif %} {% endfor %}

{% endif %}{% endfor %}

{{ t('interview.lint_hint') }}

{% if findings %}

{{ t('interview.lint_findings') }}

{% if lint_failed %}

{{ t('interview.lint_failed') }}

{% endif %}
{{ findings }}
{% endif %}

{{ t('interview.trace') }}

{{ t('interview.trace_hint') }}

{{ t('interview.trace_counts', **trace.counts) }}

{% if trace.problems %}

{{ t('interview.trace_problems') }}

    {% for problem in trace.problems %}
  • {{ problem }}
  • {% endfor %}
{% endif %} {% if not trace.verdicts %}

{{ t('interview.trace_none') }}

{% elif not trace.counts.unanchored and trace.counts.anchored == trace.verdicts|length %}

{{ t('interview.trace_clean') }}

{% endif %}
    {% for verdict in trace.verdicts %} {# The words over a backing are computed from its provenance and never written here: "you said" is true over a transcript quote and false over a line of the sheet, which the person approved and did not utter. The hint key comes from the route for the same reason. #}
  • {{ t('interview.anchor_' ~ verdict.status) }}

    {{ t('interview.' ~ verdict.hint) }}

    {{ t('interview.trace_post') }}
    {{ verdict.post }}
    {{ t('interview.trace_' ~ verdict.provenance) }}
    {{ verdict.quote }}
  • {% endfor %}
{# Not the post, and never rendered inside it. Every kind the skill asks the writing step for is listed, present or not: a list that silently holds one of two reads as a complete answer, and nobody asks again for a thing they were never told was missing. #}

{{ t('interview.notes') }}

{{ t('interview.notes_hint') }}

{{ t('interview.photos') }}
    {% for note in trace.photos %}
  • {{ t('interview.note_' ~ note.kind) }} {{ note.text }}
  • {% endfor %} {% for kind in trace.missing.photos %}
  • {{ t('interview.note_' ~ kind) }} {{ t('interview.note_missing') }}
  • {% endfor %}
{{ t('interview.tips') }}
    {% for note in trace.tips %}
  • {{ t('interview.note_' ~ note.kind) }} {{ note.text }}
  • {% endfor %} {% for kind in trace.missing.tips %}
  • {{ t('interview.note_' ~ kind) }} {{ t('interview.note_missing') }}
  • {% endfor %}
{# Outside the open guard, deliberately. One of the refusals this carries is "this interview already became a post", which by definition can only be shown on a closed one: inside the guard it was a sentence nobody could ever read. #} {% if archive_problem %}

{{ t('interview.archive_' ~ archive_problem|replace('-', '_')) }}

{% endif %} {% if conversation.state == 'open' %} {# The step the skill says decides whether any of this was worth doing. The classification is the person's: none of it is derivable from a body of text, and a value guessed here is a guess counted in every ratio. #}

{{ t('interview.archive') }}

{{ t('interview.archive_hint') }}

{{ t('interview.archive_slug_hint') }}

{{ t('interview.archive_state_hint') }}

{% endif %} {% endif %}
{% endif %} {% if bank and bank.angles %}

{{ t('interview.bank') }}

{{ t('interview.bank_hint') }}

{% endif %}

{{ t('interview.ending') }}

{{ t('interview.discard_hint', path=conversation.id) }}

{% endblock %}