agent-market/market/copyedit.py
leora d785091d64 Typed chat: hosted free models, then the local model, nothing else; busy reply + Retry; llama-server watchdog
- Remove the keyword-rules agent from typed chat: fallback_agent, parse_look_command (+ tables), the copy-edit parser
  and _rules_check are gone. If every model fails: 'The assistant is busy, please try again in a moment.' with a Retry
  button (agent 'unavailable', no actions). Clicks, forms and payments stay deterministic (fallback_event); the landing
  view is a deterministic featured-listings panel; the Sell button opens the empty form.
- Local model via llama.cpp llama-server directly (ops/local-llm-watchdog.sh, loopback only, restart on exit/hang,
  crash backoff, log rotation; deploy.sh ensures it runs). Lean: no vision projector, no weight repack (mmapped), ctx
  6144, 1 slot, q8 KV, 6 threads at nice 10, poll 0, 1 GiB prompt cache. Idle 0% CPU, 2.9 GB RSS (0.6 GB private).
- Local requests use a JSON GBNF grammar, worked examples in the cached system prompt, and a labelled visitor message.
  10/10 correct on the real prompt, warm 4-13 s.
- Primer re-primes after a model-server restart (llama-server /metrics counter) and otherwise stays idle.
- Fix: hosted calls kept the Authorization header off when a local model was configured (cfg['local'] is a dict on
  hosted configs); now only the local server itself skips the key. Test added.
- Badge/chat header: 'AI: busy', 'AI: quota used up', 'AI: unavailable'; no 'built-in'/'checked by rules' texts.
- Tests: proxy checks expect the busy reply (never rules), clicks still work while busy, no-model server, local grammar/
  prompt/no key; browser checks for busy + Retry; e2e_http accepts busy for typed turns unless REQUIRE_LLM=1;
  test_copyedit.py replaced by test_copy_defaults.py; README updated.
2026-10-08 21:58:41 +08:00

38 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Editable page text (copy slots): the defaults and what a visitor currently sees.
Used to tell the model what each text slot shows right now (PAGE_TEXT in the prompt). Text edits themselves are
made by the model with set_theme copy; there is no rule-based text editing.
"""
import os
# Keep in sync with copyDefaults in web/src/App.tsx (tests/test_copy_defaults.py checks this).
DEFAULT_COPY = {
"logo": "Agent Market",
"headline": "A marketplace you can just talk to.",
"subhead": "Tell the agent what you want. It searches, compares, lists your stuff and runs the checkout, right here on the "
"page. You can also restyle the whole page from the chat.",
"footer": "Agent Market demo · fake test data · test-mode payments · open API",
"chips": [{"label": "🚲 Show me bikes", "say": "show me bikes"}, {"label": "📐 Tutoring", "say": "find tutoring services"},
{"label": "⚖️ Compare tutoring", "say": "compare the two tutoring options"}, {"label": "➕ Sell something", "event": "sell"},
{"label": "🎨 Make it look retro", "say": "make it look retro"}],
"bullets": ["{fee}% facilitation fee when you pay online (test mode)", "$0 fee when you meet and pay cash in person",
"Agents welcome: other AI agents buy and sell through the open API (see For agents)"],
}
def current_copy(theme, fee_bps=None):
"""The text each editable slot shows right now for this visitor (defaults + their overrides)."""
fee_bps = fee_bps if fee_bps is not None else int(os.environ.get("AGENT_MARKET_FEE_BPS", "500"))
fee = f"{fee_bps / 100:.{1 if fee_bps % 100 else 0}f}"
c = {k: (list(v) if isinstance(v, list) else v) for k, v in DEFAULT_COPY.items()}
c["bullets"] = [b.replace("{fee}", fee) for b in c["bullets"]]
over = (theme or {}).get("copy") if isinstance(theme, dict) else None
for k, v in (over or {}).items() if isinstance(over, dict) else ():
if k in ("logo", "headline", "subhead", "footer") and isinstance(v, str) and v.strip():
c[k] = v
elif k == "chips" and isinstance(v, list):
c["chips"] = [x for x in v if isinstance(x, dict) and isinstance(x.get("label"), str)]
elif k == "bullets" and isinstance(v, list):
c["bullets"] = [x for x in v if isinstance(x, str)]
return c