Standalone Shonar Desktop: vendor portable sources + local engine; decouple from ~/Projects/Shonar
- shared/ = portable Android-origin sources vendored from deferred/desktop-server (app/build.gradle.kts srcDir repointed; PlaybackController.kt excluded as Android-only) - backend/ = bundled-lite engine (SQLite + inline queue); .venv symlinked from the old checkout, PYTHONPATH pins THIS backend's code over any editable install - repoRoot() resolves this project dir (env SHONAR_REPO still wins); desktop-dev.sh watches shared/ + backend/ - Verified: :app:compileKotlin + :app:test green (23 tests); engine boots on :8010, self-migrates, /healthz ok
This commit is contained in:
commit
76c867fca4
136 changed files with 21099 additions and 0 deletions
35
backend/shonar/services/ai/_llm.py
Normal file
35
backend/shonar/services/ai/_llm.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""Shared summary contract: one system prompt, one JSON shape.
|
||||
|
||||
The model replies with JSON only; partial replies are accepted and missing
|
||||
keys default to empty (a terse-but-valid summary beats a failed job).
|
||||
Transcript input is truncated to bound context — a way in, not a report.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from shonar.services.ai import SummaryResult
|
||||
|
||||
SYSTEM_PROMPT = (
|
||||
"You summarize voice recordings for the speaker's own later reference. "
|
||||
"Reply with JSON only, exactly these keys: "
|
||||
'{"short": "1-2 sentences", "detailed": "a faithful paragraph", '
|
||||
'"key_points": [], "decisions": [], "action_items": [], "questions": []}. '
|
||||
"Empty arrays when absent. Never invent names, dates, or commitments "
|
||||
"not stated in the transcript."
|
||||
)
|
||||
|
||||
MAX_TRANSCRIPT_CHARS = 12_000
|
||||
|
||||
|
||||
def build_user_message(transcript: str, title: str | None) -> str:
|
||||
text = transcript[:MAX_TRANSCRIPT_CHARS]
|
||||
if len(transcript) > MAX_TRANSCRIPT_CHARS:
|
||||
text += f"\n\n[truncated from {len(transcript)} chars]"
|
||||
head = f'Title: "{title}"\n\n' if title else ""
|
||||
return head + "Transcript:\n" + text
|
||||
|
||||
|
||||
def parse_summary(data: object, model: str) -> SummaryResult:
|
||||
if not isinstance(data, dict):
|
||||
return SummaryResult(model=model)
|
||||
return SummaryResult.from_dict(data, model=model)
|
||||
Loading…
Add table
Add a link
Reference in a new issue