personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #127 from kognova/codex/add-per-day-screen-with-actions

Add per-day admin controls

authored by

Jer Miller and committed by
GitHub
a53be6b3 10563e7a

+160
+4
dream/README.md
··· 53 53 - Events in the calendar hour view modal also link to these anchors so you can 54 54 jump straight from an event to the relevant markdown section. 55 55 56 + 57 + ### Admin view 58 + 59 + Navigate to `/admin` for index and reindex options. A specific day can be opened at `/admin/YYYYMMDD` to run repairs, ponder prompts, entity roll and screen reduction.
+10
dream/__init__.py
··· 64 64 search_page = search_view.search_page 65 65 import_page = import_page_view.import_page 66 66 admin_page = admin_view.admin_page 67 + admin_day_page = admin_view.admin_day_page 68 + admin_repair = admin_view.admin_repair 69 + admin_ponder = admin_view.admin_ponder 70 + admin_entity = admin_view.admin_entity 71 + admin_reduce = admin_view.admin_reduce 67 72 reindex = admin_view.reindex 68 73 refresh_summary = admin_view.refresh_summary 69 74 reload_entities_view = admin_view.reload_entities_view ··· 94 99 "login", 95 100 "logout", 96 101 "admin_page", 102 + "admin_day_page", 103 + "admin_repair", 104 + "admin_ponder", 105 + "admin_entity", 106 + "admin_reduce", 97 107 "reindex", 98 108 "refresh_summary", 99 109 "reload_entities_view",
+29
dream/templates/admin_day.html
··· 1 + {% extends 'base.html' %} 2 + {% set title = 'Admin ' + day %} 3 + {% set active = 'admin' %} 4 + {% block head %} 5 + <style> 6 + body { font-family: sans-serif; margin:0; } 7 + .container { max-width:600px; margin:0 auto; padding:1em; } 8 + button { padding:8px 16px; margin:0.5em 0; } 9 + pre { background:#f5f5f5; padding:0.5em; } 10 + </style> 11 + {% endblock %} 12 + {% block body %} 13 + <div class="container"> 14 + <h1>Admin {{ day }}</h1> 15 + <button id="repairBtn">Run Repairs</button> 16 + <button id="ponderBtn">Run Ponder Prompts</button> 17 + <button id="entityBtn">Entity Roll</button> 18 + <button id="reduceBtn">Screen Reduce</button> 19 + <pre id="status"></pre> 20 + </div> 21 + <script> 22 + function post(url){return fetch(url,{method:'POST'}).then(r=>r.json()).then(d=>{document.getElementById('status').textContent=JSON.stringify(d,null,2);});} 23 + 24 + document.getElementById('repairBtn').onclick=()=>post('{{ url_for('admin.admin_repair', day=day) }}'); 25 + document.getElementById('ponderBtn').onclick=()=>post('{{ url_for('admin.admin_ponder', day=day) }}'); 26 + document.getElementById('entityBtn').onclick=()=>post('{{ url_for('admin.admin_entity', day=day) }}'); 27 + document.getElementById('reduceBtn').onclick=()=>post('{{ url_for('admin.admin_reduce', day=day) }}'); 28 + </script> 29 + {% endblock %}
+67
dream/views/admin.py
··· 1 1 from __future__ import annotations 2 2 3 + import glob 4 + import os 5 + import re 6 + import subprocess 3 7 from typing import Any 4 8 5 9 from flask import Blueprint, jsonify, render_template 6 10 11 + from think import entity_roll 7 12 from think.indexer import ( 8 13 load_cache, 9 14 save_cache, ··· 12 17 scan_ponders, 13 18 ) 14 19 from think.journal_stats import JournalStats 20 + from think.reduce_screen import reduce_day 15 21 16 22 from .. import state 23 + from ..utils import DATE_RE 17 24 from ..views.entities import reload_entities 18 25 19 26 bp = Blueprint("admin", __name__, template_folder="../templates") ··· 49 56 def reload_entities_view() -> Any: 50 57 reload_entities() 51 58 return jsonify({"status": "ok"}) 59 + 60 + 61 + def _valid_day(day: str) -> bool: 62 + if not re.fullmatch(DATE_RE, day): 63 + return False 64 + if not state.journal_root: 65 + return False 66 + return os.path.isdir(os.path.join(state.journal_root, day)) 67 + 68 + 69 + def _run(cmd: list[str]) -> int: 70 + env = os.environ.copy() 71 + if state.journal_root: 72 + env["JOURNAL_PATH"] = state.journal_root 73 + result = subprocess.run(cmd, env=env) 74 + return result.returncode 75 + 76 + 77 + @bp.route("/admin/<day>") 78 + def admin_day_page(day: str) -> str: 79 + if not _valid_day(day): 80 + return "", 404 81 + return render_template("admin_day.html", active="admin", day=day) 82 + 83 + 84 + @bp.route("/admin/api/<day>/repairs", methods=["POST"]) 85 + def admin_repair(day: str) -> Any: 86 + if not _valid_day(day): 87 + return jsonify({"error": "invalid day"}), 404 88 + _run(["gemini-transcribe", "--repair", day]) 89 + _run(["screen-describe", "--repair", day]) 90 + return jsonify({"status": "ok"}) 91 + 92 + 93 + @bp.route("/admin/api/<day>/ponder", methods=["POST"]) 94 + def admin_ponder(day: str) -> Any: 95 + if not _valid_day(day): 96 + return jsonify({"error": "invalid day"}), 404 97 + think_dir = os.path.dirname(entity_roll.__file__) 98 + prompt_paths = sorted(glob.glob(os.path.join(think_dir, "ponder", "*.txt"))) 99 + for prompt in prompt_paths: 100 + _run(["ponder", day, "-f", prompt, "-p"]) 101 + return jsonify({"status": "ok"}) 102 + 103 + 104 + @bp.route("/admin/api/<day>/entity", methods=["POST"]) 105 + def admin_entity(day: str) -> Any: 106 + if not _valid_day(day): 107 + return jsonify({"error": "invalid day"}), 404 108 + day_dirs = entity_roll.find_day_dirs(state.journal_root) 109 + entity_roll.process_day(day, day_dirs, True) 110 + return jsonify({"status": "ok"}) 111 + 112 + 113 + @bp.route("/admin/api/<day>/reduce", methods=["POST"]) 114 + def admin_reduce(day: str) -> Any: 115 + if not _valid_day(day): 116 + return jsonify({"error": "invalid day"}), 404 117 + reduce_day(day) 118 + return jsonify({"status": "ok"})
+50
tests/test_admin_day_view.py
··· 1 + import importlib 2 + 3 + 4 + def test_admin_day_page(tmp_path): 5 + review = importlib.import_module("dream") 6 + (tmp_path / "20240101").mkdir() 7 + review.journal_root = str(tmp_path) 8 + with review.app.test_request_context("/admin/20240101"): 9 + html = review.admin_day_page("20240101") 10 + assert "Run Repairs" in html 11 + 12 + 13 + def test_admin_day_actions(monkeypatch, tmp_path): 14 + review = importlib.import_module("dream") 15 + (tmp_path / "20240101").mkdir() 16 + review.journal_root = str(tmp_path) 17 + called = [] 18 + 19 + monkeypatch.setattr("dream.views.admin._run", lambda cmd: called.append(cmd)) 20 + monkeypatch.setattr( 21 + "dream.views.admin.entity_roll.process_day", 22 + lambda day, dirs, force: called.append(["entity", day]), 23 + ) 24 + monkeypatch.setattr("dream.views.admin.reduce_day", lambda day: called.append(["reduce", day])) 25 + monkeypatch.setattr( 26 + "glob.glob", lambda pattern: ["prompt1.txt", "prompt2.txt"] if "ponder" in pattern else [] 27 + ) 28 + 29 + with review.app.test_request_context("/admin/api/20240101/repairs", method="POST"): 30 + resp = review.admin_repair("20240101") 31 + assert resp.json["status"] == "ok" 32 + assert ["gemini-transcribe", "--repair", "20240101"] in called 33 + 34 + called.clear() 35 + with review.app.test_request_context("/admin/api/20240101/ponder", method="POST"): 36 + resp = review.admin_ponder("20240101") 37 + assert resp.json["status"] == "ok" 38 + assert ["ponder", "20240101", "-f", "prompt1.txt", "-p"] in called 39 + 40 + called.clear() 41 + with review.app.test_request_context("/admin/api/20240101/entity", method="POST"): 42 + resp = review.admin_entity("20240101") 43 + assert resp.json["status"] == "ok" 44 + assert ["entity", "20240101"] in called 45 + 46 + called.clear() 47 + with review.app.test_request_context("/admin/api/20240101/reduce", method="POST"): 48 + resp = review.admin_reduce("20240101") 49 + assert resp.json["status"] == "ok" 50 + assert ["reduce", "20240101"] in called