Two requests on top of v0.1.9:
1. Drag-to-resize splitter
The previous fixed Bootstrap col-md-5/col-md-7 split locked the
user into 41.66% answer / 58.33% PDF. Replaced with a flex-row
layout containing a 6px drag handle. mousedown spawns a fixed
transparent overlay so the PDF.js iframe doesn't swallow mouse
events while dragging; mouseup persists the chosen ratio (clamped
15-85%) to localStorage as kb-split-pct, so the next page-load and
the next session both come back to the same layout. Both the
ask split view and the search split view share _buildSplitShell +
_wireSplitter — single source of truth for the geometry.
2. Search survives a view switch
Search had the same view-bound promise problem ask had before
v0.1.9. Hoisted the in-flight request to a module-level
_activeSearch and the most recent {query, kind, hits, selectedIdx}
to _lastSearch + sessionStorage. afterRender restores the input,
the kind filter, the hit list (with the same hit highlighted), and
the right-pane preview. Click handler updates selectedIdx so a
round-trip lands on the user's last-clicked hit, not always on the
top-ranked one.
The persistence helpers grew a tiny refactor: _loadJson/_saveJson/_clear
replace the ask-only SS_KEY shim and now back both ask + search keys.
Refs Task Master #5, #6
Two recurring frustrations from the v0.1.8 demo:
1. Searching "מהי תקנה 37" only returned the תקנה 37 חוזר and the law —
ספר הליקויים, the canonical medical reference, was crowded out by the
document whose title literally contained the query terms. Fixed in
shira-hermes 1441a41 by adding an LLM-driven query-expansion path to
/kb/search (default expand=true). Same query now also returns 2 hits
from src 29 (ספר הליקויים).
2. Asking Shira a question and clicking another EspoCRM view dropped the
in-flight request — coming back rendered a blank kb-results pane, even
though the request had completed in the background. Fixed here:
{question, promise, startedAt} are now stored at module scope (closure
shared across remounts of this view). afterRender re-binds handlers
for any active ask, resumes the progress panel with the original start
time so the elapsed counter doesn't reset, and replays the most recent
completed answer from sessionStorage so the user sees their result
even after a hard reload. State is intentionally session-scoped — no
answers persist across browser sessions.
Refs Task Master #3, #4
Search and ask modes now render results as a two-column layout: a left
column with ranked hit cards (search) or Shira's answer + source picker
(ask), and a right column with an iframe showing the source PDF scrolled
to page_number. Clicking a hit (search) or source pill (ask) swaps the
iframe's src, so users can verify a quote against the original PDF
without leaving the KB tab.
- search: renderSearchResults lays out results as panel cards on the
left (with kind + title + section + "עמ׳ N" label); the top hit is
pre-selected and its PDF loads on the right. Clicking any card
re-highlights it and swaps the preview. Law-kind hits (Wikisource
text) gracefully fall back to a chunk-text panel with a Wikisource
link so the right pane never 404s on a text source.
- ask: renderAskAnswer dedups /kb/ask's sources[] by source_id,
collects all cited pages per source, and renders a picker row plus
per-source page-jump buttons. First source's first page loads on
initial render; buttons swap the iframe without re-running the query.
Depends on shira-hermes commits e534709 + 1ca1cc0 (chunker page_number
propagation + null-byte strip) — without them, every PDF collapses to
page 1 in the DB and the jump links are cosmetic.
Refs Task Master #1
The ask mode goes through the full agent loop (hybrid search + rerank +
Claude). Typical runs land at 30-90s; long queries reach 90-120s. With
the previous UX the user saw only "Loading…" on the submit button and
"מחפש…" in the results area — indistinguishable from a stuck request.
- Render a dedicated progress panel on ask with a spinner, a description
of what shira is doing, and a live elapsed-seconds counter.
- After 2 minutes, append a "taking longer than usual" warning so users
know the request is still running.
- Override Espo.Ajax timeout to 240s; jQuery's default (undefined) or
EspoCRM's request-level default would kill the browser request well
before shira finishes on a cold cache.
- Version 0.1.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KnowledgeBase extension for EspoCRM: UI on top of the shira-hermes KB of
Israeli National Insurance law, regulations, and circulars.
Three modes:
- חיפוש — Hybrid (pgvector + tsvector) + Voyage rerank-2.5, returns ranked
chunks with heading_path and citations.
- שאל את שירה — Full agent loop; Shira picks up search_insurance_kb as
needed and returns a summary with citations.
- עיון — Browse all active sources. Click a source:
- PDF source (ספר הליקויים, ספר המבחנים, circulars): renders the
original PDF inline via an iframe proxied through the
KnowledgeBasePdf EntryPoint, so the layout/columns/tables are
preserved and Ctrl+F works natively.
- TXT source (חוק הביטוח הלאומי scraped from Wikisource): falls back
to the hierarchical chunk list with RTL styling.
Architecture:
- Controller: KnowledgeBase.php — thin proxy to shira-hermes /kb/*.
- Service: KnowledgeBaseService.php — shared curl plumbing; derives the
shira-hermes base URL from the SmartAssistant integration record so
there is no second admin config.
- EntryPoint: KnowledgeBasePdf.php — streams the PDF inline, wraps the
body in a php://temp stream for setBody, applies a locked-down CSP.
- JS: views/kb/index.js branches on source.original_path; modes wired
through the SmartAssistant fa_IR i18n convention.
Auth model:
- Browser → EspoCRM: session cookie / X-Api-Key (EspoCRM's existing auth).
- EspoCRM → shira-hermes: X-Api-Key from the SmartAssistant integration
(never exposed to the browser).
- Portal users are blocked at both the Controller and the EntryPoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>