Commit Graph

8 Commits

Author SHA1 Message Date
chaim 6f11895dd7 chore(tm): file #17 — production deployment plan
Comprehensive Coolify-prod runbook covering: pgvector PG provisioning,
MinIO bucket, shira-hermes dockerimage app, Infisical /prod secrets,
data migration choice (pg_dump+S3 mirror vs fresh ingest), KB extension
install on prod EspoCRM, SmartAssistant integration config,
optional n8n auto-scan workflow clone, DNS, and end-to-end smoke test
sequence. Currently the entire stack is dev-only — this task makes
KB live on the production CRM at crm.prod.marcus-law.co.il.
2026-04-25 13:45:44 +00:00
chaim 63d7a7922a chore(tm): file 8 new tasks — 3 carry-over + 5-phase multi-topic plan
Captures the open-but-not-blocking items from the v0.2.1 sessions plus
the comprehensive plan for generalizing the KB beyond ביטוח לאומי.

#9-11: carry-over polish noted at the v0.2.1 close
- #9  SSE rejoin (in-flight stream survives F5 / tab close)
- #10 n8n release-asset auto-attach has been failing for every release
      from v0.1.8 through v0.2.1; manual curl uploads as workaround
- #11 ask mode source picker hides text-source citations (Wikisource law)
      because /kb/ask filters sources_used to PDFs only

#12-16: multi-topic KB plan (next session)
- #12 Phase 1 — DB schema with kb_topic + kb_source.topic_id, /kb/topics
      endpoint, topic-aware system prompts, topic dropdown in UI;
      backwards-compat by seeding topic_id=1 ביטוח לאומי and migrating
      the 6 existing sources to it
- #13 Phase 2 — file upload from the management panel, async ingestion
      via kb_ingest_job table, live job-status polling
- #14 Phase 3 — sources management UI: edit metadata, delete (with
      cascade messaging), re-ingest, jobs/failures view
- #15 Phase 4 — topic CRUD UI for admins (add דיני עבודה, דין פלילי,
      etc., edit prompts, soft-disable)
- #16 Phase 5 (optional) — per-topic ACL by EspoCRM role with view vs
      manage permissions

Phases 1-3 are the must-ship subset for what the user asked. Phase 4 is
admin convenience. Phase 5 is scoped only if firms ask for it.
2026-04-25 13:33:51 +00:00
chaim a00fc3729c feat(kb/ask): live SSE streaming of Shira's progress (v0.2.0)
User wanted to see what Shira is doing while she thinks — like Claude
does — instead of staring at an incrementing seconds counter. Now the
ask UI shows one line per agent step as it happens:

  3s  🧠  קוראת את השאלה
  4s  🔍  מחפשת בבסיס הידע: "תקנה 36"
  9s  ✓   נמצאו 5 קטעים רלוונטיים
 10s  🔍  מחפשת בבסיס הידע: "תקנה 36 ביטוח לאומי"
 14s  ✓   נמצאו 5 קטעים רלוונטיים
 15s  🧠  ממשיכה לחקור
 22s  ✍️  מנסחת תשובה
 → תשובה

Client wiring:
- runAsk now opens an EventSource at ?entryPoint=KnowledgeBaseAskStream
  instead of POSTing JSON. Each SSE event of type thinking/tool_start/
  tool_done/tool_error/writing appends a row to a stacked progress log.
  The final {type:answer,text,sources} event triggers renderAskAnswer
  with the existing split view. {type:error} surfaces via showError.
- Module-level state from v0.1.9 unchanged in spirit but now stores the
  EventSource + accumulated events. afterRender on view re-mount replays
  the entire event list and re-binds onmessage/onerror — the SSE
  connection itself doesn't drop because it lives at module scope.
- Elapsed-time pill stays for cadence but is decoration; the real
  signal is the per-event log.

EspoCRM proxy:
- New EntryPoint KnowledgeBaseAskStream — required because EventSource
  is GET-only and ?message= must come via the URL. PHP buffering is
  disabled (zlib + ob + apache_setenv no-gzip), Apache headers are set
  for SSE (no-cache, X-Accel-Buffering: no), and we cURL into
  shira-hermes /kb/ask/stream with CURLOPT_WRITEFUNCTION echoing each
  byte and flushing immediately. The handler ends with exit; to bypass
  EspoCRM's Response wrapper which would otherwise emit headers/body
  on top of our raw stream.
- KnowledgeBaseService gained two public accessors (getStreamingUrl,
  getStreamingApiKey) so the EntryPoint can build the cURL without
  going through request() (which buffers the whole body).
- entryPoints.json registers the new class.

Server (shira-hermes 76fd77f):
- AgentRunner.run accepts an on_event sync callback fired before each
  LLM call (thinking) and around each tool call (tool_start, tool_done,
  tool_error). Hebrew labels in agent_runner._tool_label / _tool_done_label
  ship over the wire ready-to-display — keeps the client free of
  translation concerns.
- POST /kb/ask/stream wraps the existing agent runner in an asyncio
  task feeding an asyncio.Queue, returns StreamingResponse with proper
  SSE headers (X-Accel-Buffering, Cache-Control). 15s comment-line
  heartbeats keep the connection open through proxy idle-timeouts.
- Original POST /kb/ask still works — additive change.

Refs Task Master #8
2026-04-25 13:00:58 +00:00
chaim 2fe10727eb fix(kb): search preview shows section-in-context for text sources
User reported the search card "looks broken" when the selected hit is the
law (Wikisource source 5) — the right preview pane just duplicates the
chunk text already visible in the left list card. For PDF sources the
right pane shows the PDF page, but for text-only sources it had nothing
useful to add and fell back to a copy.

Fix:
- /kb/search hits gained chunk_index (shira-hermes b127caf), and
  /kb/source/{id}/chunks gained an `around=<chunk_index>&ctx=<k>` window.
- Espo proxy plumbing: getActionChunks reads `around`/`ctx` query
  params and forwards them; KnowledgeBaseService::sourceChunks accepts
  optional $around + $ctx and appends them to the upstream URL.
- showSearchPreview for non-PDF hits now fetches the matched chunk plus
  2 sections before and 2 after, renders them as stacked panels: the
  matched section gets a yellow #fff3cd highlight, neighbors render as
  muted context blocks. The panel header carries a prominent
  "פתח ב-Wikisource" button so users can jump to the public source page.
  After render the matched section auto-scrolls into view.
- Race protection via _previewToken: a click on a different hit while a
  fetch is in flight cancels the stale render so the user always sees
  the section that matches the currently-selected card.

PDF path unchanged.

Refs Task Master #7
2026-04-25 12:28:50 +00:00
chaim ac5823ffe5 feat(kb): drag-to-resize splitter + search survives view switch
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
2026-04-25 10:46:39 +00:00
chaim 50a60c13b3 feat(kb): ask survives view switch + search query expansion (server-side)
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
2026-04-25 10:23:49 +00:00
chaim eb1b0fc7d4 chore: mark task #1 done after v0.1.8 release 2026-04-24 16:01:03 +00:00
chaim 243ae2353f feat(kb): split-view PDF jump-to-page for both ask and search modes
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
2026-04-24 15:49:29 +00:00