{% extends "base.html" %} {% from "_help.html" import info %} {% from "_job_status.html" import job_status %} {% block title %}Index — Paperful{% endblock %} {% block body_attrs %}{% if poll_run %} data-poll-run="{{ poll_run }}"{% endif %}{% endblock %} {% block content %}

Index

{% if error %}
{{ error }}
{% endif %}

{{ message }} The index is built from PDFs already copied to disk under out/, not live from Zotero. Ingest first, then Search here or Ask.

{% if flags.remote_llm %}

Remote LLM — excerpts from your PDFs leave this machine.

{% endif %} {% if flags.remote_embed %}

Remote embedding model — passage text leaves this machine.

{% endif %} {% if enabled %}
Status

How many items and chunks are in the local index. If this is empty, run Ingest.

{% for k, v in status.items() %} {% endfor %}
FieldValue
{{ k }}{{ v }}
{% endif %} {% if flags.can_ingest %}

Ingest

Read PDFs (and abstracts) from the mirror into the search index. Dry-run counts what would change. Unchanged files are skipped unless you come back after they change on disk.

{{ job_status(ingest_result, 'Ingest') }} {% if ingest_result.summary %}

{% if ingest_result.dry_run %}Would index{% else %}Indexed{% endif %} {{ ingest_result.summary.pdf + ingest_result.summary.ocr }} PDFs ({{ ingest_result.summary.ocr }} after OCR) and {{ ingest_result.summary.abstract }} abstracts. Unchanged {{ ingest_result.summary.unchanged }}, failed {{ ingest_result.summary.failed }}. pdf {{ ingest_result.summary.pdf }}, chunks {{ ingest_result.summary.chunks }}.

{% endif %}
{{ info("Count what would be indexed. Writes nothing.") }} {{ info("Write new or changed PDFs and abstracts into the local index.") }}
{% endif %} {% if flags.can_search %}

Search

Find passages in the indexed PDFs. This is retrieval only — it does not call the chat model. Ask uses the same index to answer in sentences.

{{ info("Retrieve matching passages. Does not call the chat model.") }}
{% if hits %} {% for h in hits %} {% endfor %}
ScoreTitleExcerpt
{{ h.score }}{{ h.title }} [{{ h.item_key }}]{{ h.text }}
{% endif %}
{% endif %} {% if flags.llm_on and flags.rag_on %}

Extract questions

Pull research questions out of indexed papers. The default is a rules pass over headings and abstracts. The LLM extract lane also asks the model, which costs tokens and may send excerpts off this machine if the model is remote.

{{ job_status(questions_result, 'Extract questions') }} {% if questions_result.questions is defined %}

Last: {{ questions_result.questions }} question(s) from {{ questions_result.items }} item(s){% if questions_result.dry_run %} (dry-run){% endif %}

{% endif %}
{% include "_rag_scope_fields.html" %}

Extract {{ info("Rules first; optionally a grounded LLM pass. Dry-run counts without writing.") }}

{{ info("Count questions that would be written. Does not save them.") }} {{ info("Write extracted questions under state/rag/questions/.") }}

Already answered?

Check whether the indexed corpus already answers a list of questions. Useful before a new study: you get answered / partial / not found rather than a full write-up.

{{ job_status(answered_result, 'Answered check') }} {% if answered_result.stamp %}

Last: answered {{ answered_result.answered }} · partial {{ answered_result.partial }} · not found {{ answered_result.not_found }} report

{% endif %}

Questions {{ info("Pick one source of questions. The form, an upload, or questions already extracted above.") }}

{% include "_rag_scope_fields.html" %}

Check {{ info("Walk the index and classify each question as answered, partial, or not found.") }}

{{ info("Classify each question against the index. Spends tokens when the LLM lane is on.") }}
{% if answered_packs %}
Recent answered reports ({{ answered_packs|length }})
{% endif %}

Synthesize report

Fold existing summary notes into a literature-review report for the current collection. Preview (dry-run) shows source counts and the chunk plan without calling the model.

{% if synthesize_result.status == 'queued' or synthesize_result.status == 'running' %}

{% if synthesize_result.args.dry_run %}Preview{% else %}Write report{% endif %} {{ synthesize_result.status }}…

{% elif synthesize_result.skip_plain %}

{{ synthesize_result.skip_plain }}

{% elif synthesize_result.slug %}

Last: {% if synthesize_result.dry_run or synthesize_result.args.dest == 'zotero' %}{{ synthesize_result.slug }}{% else %}{{ synthesize_result.slug }}{% endif %} {% if synthesize_result.skipped %}({{ synthesize_result.skipped }}){% endif %}

{% elif synthesize_result.error %}

{{ synthesize_result.error }}

{% endif %}
{{ info("Show source counts and the chunk plan. No model call.") }} {{ info("Call the model and write the report to Dest.") }}
{% if reports %}
Recent reports ({{ reports|length }})
{% endif %}
{% endif %} {% endblock %}