agent-memory-brain 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_memory_brain-0.2.0.dist-info/METADATA +98 -0
- agent_memory_brain-0.2.0.dist-info/RECORD +54 -0
- agent_memory_brain-0.2.0.dist-info/WHEEL +4 -0
- agent_memory_brain-0.2.0.dist-info/entry_points.txt +3 -0
- agent_memory_brain-0.2.0.dist-info/licenses/LICENSE +67 -0
- devmem/__init__.py +3 -0
- devmem/ado.py +433 -0
- devmem/agent_colors.py +74 -0
- devmem/cli.py +500 -0
- devmem/db.py +1069 -0
- devmem/dispatch.py +478 -0
- devmem/embed.py +111 -0
- devmem/filelock.py +53 -0
- devmem/git_sync.py +261 -0
- devmem/guard.py +214 -0
- devmem/hook_log.py +49 -0
- devmem/ingest/__init__.py +1 -0
- devmem/ingest/claude.py +87 -0
- devmem/ingest/codex.py +175 -0
- devmem/ingest/runner.py +318 -0
- devmem/ingest/subagents.py +250 -0
- devmem/maintenance.py +801 -0
- devmem/mcp_server.py +542 -0
- devmem/messages.py +684 -0
- devmem/panel/__init__.py +1 -0
- devmem/panel/app.py +971 -0
- devmem/panel/state.py +358 -0
- devmem/panel/static/base.css +27 -0
- devmem/panel/static/brain.css +473 -0
- devmem/panel/static/brain.js +1118 -0
- devmem/panel/static/brain3d.js +600 -0
- devmem/panel/static/chat.js +18 -0
- devmem/panel/static/common.js +208 -0
- devmem/panel/static/site.css +508 -0
- devmem/panel/static/tokens.css +19 -0
- devmem/panel/templates/ado.html +62 -0
- devmem/panel/templates/agents.html +35 -0
- devmem/panel/templates/audit.html +19 -0
- devmem/panel/templates/base.html +42 -0
- devmem/panel/templates/brain.html +101 -0
- devmem/panel/templates/maintenance.html +32 -0
- devmem/panel/templates/memory.html +71 -0
- devmem/panel/templates/message_thread.html +49 -0
- devmem/panel/templates/messages.html +8 -0
- devmem/panel/templates/overview.html +109 -0
- devmem/panel/templates/project.html +48 -0
- devmem/panel/templates/projects.html +21 -0
- devmem/panel/templates/session.html +49 -0
- devmem/panel/templates/sessions.html +67 -0
- devmem/panel/templates/subagent.html +27 -0
- devmem/procs.py +10 -0
- devmem/projects.py +17 -0
- devmem/secrets.py +9 -0
- devmem/skills.py +105 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{% block title %}Brain{% endblock %}</title>
|
|
7
|
+
<meta name="description" content="Live view of devmem: agent activity, messages, memory and usage limits.">
|
|
8
|
+
<link rel="preconnect" href="https://api.fontshare.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=switzer@400,500&display=swap">
|
|
12
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400&display=swap">
|
|
13
|
+
<link rel="stylesheet" href="{{ asset('site.css') }}">
|
|
14
|
+
{% block head %}{% endblock %}
|
|
15
|
+
</head>
|
|
16
|
+
<body class="{% block body_class %}{% endblock %}">
|
|
17
|
+
<header class="topbar">
|
|
18
|
+
<a class="brand" href="/" aria-label="Brain, home">Brain</a>
|
|
19
|
+
<nav>
|
|
20
|
+
<a href="/" class="{{ 'on' if active == 'brain' }}">Brain</a>
|
|
21
|
+
<a href="/overview" class="{{ 'on' if active == 'overview' }}">Overview</a>
|
|
22
|
+
<a href="/projects" class="{{ 'on' if active in ('projects', 'project') }}">Projects</a>
|
|
23
|
+
<a href="/sessions" class="{{ 'on' if active in ('sessions', 'session') }}">Sessions</a>
|
|
24
|
+
<a href="/agents" class="{{ 'on' if active == 'agents' }}">Agents</a>
|
|
25
|
+
<a href="/messages" class="{{ 'on' if active == 'messages' }}">Messages <span class="msg-badge" id="nav-msg-badge" title="Unread questions and open threads addressed to you"{% if not messages_badge %} hidden{% endif %}>{{ messages_badge or 0 }}</span></a>
|
|
26
|
+
<a href="/memory" class="{{ 'on' if active == 'memory' }}">Memory</a>
|
|
27
|
+
<a href="/audit" class="{{ 'on' if active == 'audit' }}">Audit</a>
|
|
28
|
+
<a href="/maintenance" class="{{ 'on' if active == 'maintenance' }}">Maintenance{% if maintenance_count %} <span class="ado-change-badge" title="Applied changes or open flags">!</span>{% endif %}</a>
|
|
29
|
+
<a href="/ado" class="{{ 'on' if active == 'ado' }}">ADO <span class="ado-change-badge" hidden title="ADO changed since you last looked">!</span></a>
|
|
30
|
+
</nav>
|
|
31
|
+
<span class="spacer"></span>
|
|
32
|
+
{% block topbar_extra %}{% endblock %}
|
|
33
|
+
</header>
|
|
34
|
+
{% block main %}
|
|
35
|
+
<main class="page">
|
|
36
|
+
{% block body %}{% endblock %}
|
|
37
|
+
</main>
|
|
38
|
+
{% endblock %}
|
|
39
|
+
<script src="{{ asset('common.js') }}"></script>
|
|
40
|
+
{% block scripts %}{% endblock %}
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Brain{% endblock %}
|
|
3
|
+
{% block head %}
|
|
4
|
+
<link rel="stylesheet" href="{{ asset('brain.css') }}">
|
|
5
|
+
{% endblock %}
|
|
6
|
+
{% block body_class %}wall-page{% endblock %}
|
|
7
|
+
{% block topbar_extra %}
|
|
8
|
+
<span class="clock" id="clock">--:--</span>
|
|
9
|
+
<button class="ghost" id="dim-toggle" type="button" title="Switch between the 3D web and the 2D brain"><span class="seg">2D</span><span class="seg active">3D</span></button>
|
|
10
|
+
<button class="ghost" id="glow-toggle" type="button" title="Toggle 3D bloom glow (3D view only)" aria-pressed="false">○ Glow off</button>
|
|
11
|
+
<button class="ghost" id="wall-toggle" type="button" title="Wall mode: hide navigation (key W)">Wall mode</button>
|
|
12
|
+
{% endblock %}
|
|
13
|
+
{% block main %}
|
|
14
|
+
<div class="status" id="status" data-state="connecting"><span class="dot"></span><span class="txt">connecting</span></div>
|
|
15
|
+
<button class="wall-exit" id="wall-exit" type="button" title="Leave wall mode (W or Esc)">☰ Menu</button>
|
|
16
|
+
<div class="wall-hint" id="wall-hint" hidden>W / Esc to exit wall mode</div>
|
|
17
|
+
|
|
18
|
+
<main class="wall">
|
|
19
|
+
<section class="col col-left">
|
|
20
|
+
<div class="panel panel-limits">
|
|
21
|
+
<h3>Limits <span class="hint">% left</span></h3>
|
|
22
|
+
<div class="gauges" id="gauges"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="panel panel-memory">
|
|
25
|
+
<h3>Memory</h3>
|
|
26
|
+
<div class="mem-total"><span id="mem-total">0</span><small>memories</small></div>
|
|
27
|
+
<div class="kinds" id="kinds"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<section class="panel panel-ado" id="ado-card" hidden>
|
|
30
|
+
<h3 id="ado-heading"><a href="/ado">ADO</a></h3>
|
|
31
|
+
<a class="ado-card-counts" id="ado-counts" href="/ado" aria-label="Open Azure DevOps summary"></a>
|
|
32
|
+
</section>
|
|
33
|
+
</section>
|
|
34
|
+
|
|
35
|
+
<section class="col col-centre">
|
|
36
|
+
<div class="centre-top">
|
|
37
|
+
<div class="panel panel-reminders"><h3>Reminders <span class="hint" id="reminders-count"></span></h3><ul class="reminders" id="reminders"></ul></div>
|
|
38
|
+
<div class="brain-wrap" id="brain-wrap">
|
|
39
|
+
<canvas id="brain" aria-label="Live memory and agent network"></canvas>
|
|
40
|
+
<div class="brain3d" id="brain3d" hidden aria-label="Live memory and agent network, 3D"></div>
|
|
41
|
+
<div class="brain-tip" id="brain-tip" hidden></div>
|
|
42
|
+
<div class="brain-legend">
|
|
43
|
+
<span><i style="--c:var(--color-claude)"></i>Claude</span>
|
|
44
|
+
<span><i style="--c:var(--color-subagent)"></i>subagent</span>
|
|
45
|
+
<span><i style="--c:var(--color-codex)"></i>Codex</span>
|
|
46
|
+
<span class="sep" aria-hidden="true"></span>
|
|
47
|
+
<span><i style="--c:var(--kind-note)"></i>note</span>
|
|
48
|
+
<span><i style="--c:var(--kind-decision)"></i>decision</span>
|
|
49
|
+
<span><i style="--c:var(--kind-preference)"></i>preference</span>
|
|
50
|
+
<span><i style="--c:var(--kind-snippet)"></i>snippet</span>
|
|
51
|
+
<span><i style="--c:var(--kind-todo)"></i>todo</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="panel panel-messages">
|
|
56
|
+
<h3><a href="/messages" title="Open the chats">Messages</a> <span class="hint" id="msg-unread"></span></h3>
|
|
57
|
+
<ul class="msgs" id="msgs"></ul>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section class="col col-right">
|
|
62
|
+
<section class="card">
|
|
63
|
+
<h3>Rules</h3>
|
|
64
|
+
{% if rules.error %}<p class="muted">Rules unavailable: {{ rules.error }}</p>
|
|
65
|
+
{% elif rules.rules or rules.text_rules %}
|
|
66
|
+
<table><tbody>{% for rule in rules.rules %}<tr><td class="mono">#{{ rule.id }}</td><td><span class="badge">{{ rule.action }}</span></td><td class="mono">{{ rule.tool }} {{ rule.command or rule.path or '' }}</td></tr>{% endfor %}</tbody></table>
|
|
67
|
+
{% for rule in rules.text_rules %}<p><span class="mono">#{{ rule.id }}</span> {{ rule.text }}</p>{% endfor %}
|
|
68
|
+
{% else %}<p class="muted">No active rules.</p>{% endif %}
|
|
69
|
+
{% for item in rules.invalid %}<p class="muted mono">#{{ item.id }} invalid: {{ item.reason }}</p>{% endfor %}
|
|
70
|
+
{% for item in rules.notices %}<p class="muted mono">#{{ item.id }} notice: {{ item.reason }}</p>{% endfor %}
|
|
71
|
+
</section>
|
|
72
|
+
<section class="card">
|
|
73
|
+
<h3>Skills</h3>
|
|
74
|
+
{% if skills.skills %}
|
|
75
|
+
<table><tbody>{% for skill in skills.skills %}<tr><td class="mono">{{ skill.name }}</td><td>{{ skill.description }}{% if skill.briefing %} <span class="badge">briefing</span>{% endif %}</td></tr>{% endfor %}</tbody></table>
|
|
76
|
+
{% else %}<p class="muted">No skills found.</p>{% endif %}
|
|
77
|
+
{% for item in skills.invalid %}<p class="muted mono">{{ item.path }} invalid: {{ item.reason }}</p>{% endfor %}
|
|
78
|
+
</section>
|
|
79
|
+
<div class="panel panel-agents">
|
|
80
|
+
<h3>Agents <span class="hint" id="agents-hint"></span></h3>
|
|
81
|
+
<ul class="agents" id="agents"></ul>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="panel panel-feed">
|
|
84
|
+
<h3>Live activity</h3>
|
|
85
|
+
<ul class="feed" id="feed"></ul>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
</main>
|
|
89
|
+
{% endblock %}
|
|
90
|
+
{% block scripts %}
|
|
91
|
+
<!-- Two CDN loads: 3d-force-graph 1.73.3 and three 0.162.0 bloom; bloom failure only disables glow. -->
|
|
92
|
+
<script src="https://cdn.jsdelivr.net/npm/3d-force-graph@1.73.3/dist/3d-force-graph.min.js" crossorigin="anonymous"></script>
|
|
93
|
+
<script src="{{ asset('brain3d.js') }}"></script>
|
|
94
|
+
<script type="module">
|
|
95
|
+
// Dynamic import so a load failure can be caught and signalled to brain3d.js.
|
|
96
|
+
import("https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/postprocessing/UnrealBloomPass.js/+esm")
|
|
97
|
+
.then(function (m) { window.__devmemBloom = m.UnrealBloomPass; window.dispatchEvent(new Event("devmem-bloom")); })
|
|
98
|
+
.catch(function (e) { window.__devmemBloomError = "bloom module failed to load: " + ((e && e.message) || e); window.dispatchEvent(new Event("devmem-bloom-failed")); });
|
|
99
|
+
</script>
|
|
100
|
+
<script src="{{ asset('brain.js') }}"></script>
|
|
101
|
+
{% endblock %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Maintenance · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Maintenance</h1>
|
|
5
|
+
{% if notice %}<p class="card">{{ notice }}</p>{% endif %}
|
|
6
|
+
{% if failing %}<p class="card maintenance-failing"><strong>Automatic maintenance is failing</strong> since {{ failing.since }}: {{ failing.error }}</p>{% endif %}
|
|
7
|
+
<form method="post" action="/maintenance/cleanup"><button>Clean up</button></form>
|
|
8
|
+
<section class="card">
|
|
9
|
+
<h2>GitHub sync</h2>
|
|
10
|
+
<p>{% if git_status.enabled %}Enabled{% else %}Disabled{% endif %}{% if git_status.repo %} · <span class="mono">{{ git_status.repo }}</span>{% endif %}</p>
|
|
11
|
+
<p class="muted">Last sync: {{ (git_status.last_sync | when) or 'not yet' }} · pushed {{ git_status.pushed or 0 }} · pulled {{ git_status.pulled or 0 }}</p>
|
|
12
|
+
{% if git_status.error %}<p class="muted">{{ git_status.error }}</p>{% endif %}
|
|
13
|
+
{% if git_status.out_of_sync %}<p class="muted">Out of sync: {{ git_status.out_of_sync | join(', ') }}</p>{% endif %}
|
|
14
|
+
<form method="post" action="/git/sync"><button type="submit">Sync now</button></form>
|
|
15
|
+
</section>
|
|
16
|
+
<h2>Runs</h2>
|
|
17
|
+
<table><thead><tr><th>Time</th><th>Trigger</th><th>Project</th><th>Checked</th><th>Applied</th><th>Flags</th><th>Status</th><th>Error / note</th></tr></thead><tbody>
|
|
18
|
+
{% for x in runs %}<tr><td class="when"><time datetime="{{ x.started_at }}" title="{{ x.started_at }}">{{ x.started_at | when }}</time></td><td>{{ x.trigger }}</td><td>{{ x.project or 'all' }}</td><td>{{ x.checked }}</td><td>{{ x.applied }}</td><td>{{ x.flagged }}</td><td>{{ x.status }}</td><td>{% if x.error %}<span class="run-error">{{ x.error }}</span>{% endif %}{% if x.note %} <span class="muted">{{ x.note }}</span>{% endif %}</td></tr>{% else %}<tr><td colspan="8" class="muted">No maintenance runs.</td></tr>{% endfor %}
|
|
19
|
+
</tbody></table>
|
|
20
|
+
<h2>Changes</h2>
|
|
21
|
+
{% for x in changes %}<article class="card change-card"><h3>#{{ x.memory_id }}{% if x.target_id %} → #{{ x.target_id }}{% endif %} <small>{{ x.action }}</small>{% if x.run_status == 'dry_run' %} <small>(dry run)</small>{% elif x.reverted_at %} <small>({{ 'reverted' if x.applied else 'dismissed' }})</small>{% endif %}</h3>
|
|
22
|
+
<p>{{ x.summary }}{% if x.blocked_by and x.blocked_by != 'dry_run' %} <span class="muted">Blocked: {{ x.blocked_by }}</span>{% endif %}</p>
|
|
23
|
+
{% if x.blocked_by == 'protected' %}<p class="muted">Your rule may be outdated: review it and edit it yourself on the <a href="/memory">Memory page</a>.</p>{% endif %}
|
|
24
|
+
{% if x.old_title or x.new_title %}<pre>− {{ x.old_title or '' }}
|
|
25
|
+
+ {{ x.new_title or '' }}</pre>{% endif %}
|
|
26
|
+
{% if x.old_content or x.new_content %}<pre>− {{ x.old_content or '' }}
|
|
27
|
+
+ {{ x.new_content or '' }}</pre>{% endif %}
|
|
28
|
+
{% if x.old_review_after or x.new_review_after %}<p class="muted">review_after: {{ x.old_review_after or '—' }} → {{ x.new_review_after or 'unchanged' }}</p>{% endif %}
|
|
29
|
+
<ul>{% for e in x.evidence_items %}<li>{{ e.ref }}: {{ e.detail or e.result }}{% if e.commit %} ({{ e.commit }}){% endif %}{% if e.source == 'model' %} <span class="muted">(model)</span>{% endif %}</li>{% endfor %}</ul>
|
|
30
|
+
{% if x.applied and not x.reverted_at %}<form method="post" action="/maintenance/{{ x.id }}/revert"><button>Revert</button></form>
|
|
31
|
+
{% elif x.open_flag %}<form method="post" action="/maintenance/{{ x.id }}/dismiss"><button>Dismiss</button></form>{% endif %}</article>{% else %}<p class="muted">No changes.</p>{% endfor %}
|
|
32
|
+
{% endblock %}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Memory · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Memory</h1>
|
|
5
|
+
|
|
6
|
+
<form method="get" class="form-row">
|
|
7
|
+
<input name="q" value="{{ q }}" placeholder="Search memories" size="32">
|
|
8
|
+
<select name="kind">
|
|
9
|
+
<option value="">All kinds</option>
|
|
10
|
+
{% for c in counts %}
|
|
11
|
+
<option {% if kind == c.kind %}selected{% endif %}>{{ c.kind }}</option>
|
|
12
|
+
{% endfor %}
|
|
13
|
+
</select>
|
|
14
|
+
<button>Search</button>
|
|
15
|
+
</form>
|
|
16
|
+
|
|
17
|
+
{% if embedder_status %}<p class="muted search-note">Semantic search is unavailable ({{ embedder_status.last_error }}). Showing keyword results only.</p>{% endif %}
|
|
18
|
+
|
|
19
|
+
<div class="pill-row">
|
|
20
|
+
{% for c in counts %}
|
|
21
|
+
<span class="badge kind-{{ c.kind }}">{{ c.kind }} <span class="count">{{ c.count }}</span></span>
|
|
22
|
+
{% endfor %}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<details class="card create-card">
|
|
26
|
+
<summary>Create memory</summary>
|
|
27
|
+
<form method="post">
|
|
28
|
+
<div class="form-row">
|
|
29
|
+
<input name="title" placeholder="Title">
|
|
30
|
+
<select name="kind">
|
|
31
|
+
<option>note</option><option>decision</option><option>preference</option><option>snippet</option><option>todo</option>
|
|
32
|
+
</select>
|
|
33
|
+
<input name="tags" placeholder="tags">
|
|
34
|
+
<input name="project" placeholder="project path (optional)" size="36">
|
|
35
|
+
<input name="review_after" type="date" title="Review date for time-bound notes">
|
|
36
|
+
</div>
|
|
37
|
+
<textarea name="content" required></textarea>
|
|
38
|
+
<button>Save</button>
|
|
39
|
+
</form>
|
|
40
|
+
</details>
|
|
41
|
+
|
|
42
|
+
{% for x in rows %}
|
|
43
|
+
<details class="card memory-row">
|
|
44
|
+
<summary>
|
|
45
|
+
<span class="badge kind-{{ x.kind }}">{{ x.kind }}</span>
|
|
46
|
+
{% if x.match %}<span class="badge">{{ x.match }}</span>{% endif %}
|
|
47
|
+
{% if x.stale %}<span class="badge">stale</span>{% endif %}
|
|
48
|
+
{% if x.synced %}<span class="badge">synced</span>{% endif %}
|
|
49
|
+
<span class="m-title">{{ x.title }}</span>
|
|
50
|
+
<span class="m-meta"><span class="mono">{{ x.project or 'global' }}</span><time data-rel datetime="{{ x.updated_at }}">{{ x.updated_at }}</time></span>
|
|
51
|
+
</summary>
|
|
52
|
+
<form method="post" action="/memory/{{ x.id }}">
|
|
53
|
+
<div class="form-row">
|
|
54
|
+
<input name="title" value="{{ x.title }}" size="40">
|
|
55
|
+
<select name="kind">
|
|
56
|
+
{% for k in ['note', 'decision', 'preference', 'snippet', 'todo'] %}
|
|
57
|
+
<option {% if x.kind == k %}selected{% endif %}>{{ k }}</option>
|
|
58
|
+
{% endfor %}
|
|
59
|
+
</select>
|
|
60
|
+
<input name="tags" value="{{ x.tags }}" placeholder="tags">
|
|
61
|
+
<input name="review_after" type="date" value="{{ x.review_after or '' }}" title="Review date for time-bound notes">
|
|
62
|
+
</div>
|
|
63
|
+
<textarea name="content">{{ x.content }}</textarea>
|
|
64
|
+
<button>Update</button>
|
|
65
|
+
</form>
|
|
66
|
+
<form method="post" action="/memory/{{ x.id }}/delete">
|
|
67
|
+
<button class="danger">Delete</button>
|
|
68
|
+
</form>
|
|
69
|
+
</details>
|
|
70
|
+
{% endfor %}
|
|
71
|
+
{% endblock %}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Thread · Brain{% endblock %}
|
|
3
|
+
{% macro tools(agent, open=False) %}
|
|
4
|
+
<details class="tool-lines"{% if open %} open{% endif %}><summary><span class="meta"><span>{{ agent.tools | length }} tool calls</span><span>{{ agent.status }}</span>{% if agent.tokens %}<span><span data-tokens="{{ agent.tokens }}">{{ agent.tokens }}</span> tokens</span>{% endif %}</span></summary>
|
|
5
|
+
<ol>{% for t in agent.tools %}<li><span class="mono tool">{{ t.name }}</span> <span class="muted">{{ t.arg }}</span></li>{% else %}<li class="muted">No tool calls yet.</li>{% endfor %}</ol></details>
|
|
6
|
+
{% endmacro %}
|
|
7
|
+
{% block body %}
|
|
8
|
+
<div class="page-title-row">
|
|
9
|
+
<h1>{{ root.subject or root.body[:80] }}</h1>
|
|
10
|
+
<div class="form-row">
|
|
11
|
+
<span class="badge st-{{ root.status }}">{{ root.status }}</span>
|
|
12
|
+
{% if root.status == 'closed' %}
|
|
13
|
+
<form method="post" action="/messages/thread/{{ root.id }}/reopen"><button>Reopen</button></form>
|
|
14
|
+
{% else %}
|
|
15
|
+
<form method="post" action="/messages/thread/{{ root.id }}/close"><button>Close thread</button></form>
|
|
16
|
+
{% endif %}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<p class="muted back-row"><a href="/messages{% if chat_key %}?chat={{ chat_key | urlencode }}{% endif %}">Back to chat</a>{% if root.project %}<span class="mono">{{ root.project }}</span>{% endif %}</p>
|
|
20
|
+
|
|
21
|
+
{% for m in items %}
|
|
22
|
+
<article class="card msg msg-{{ m.kind }}">
|
|
23
|
+
<header class="msg-head">
|
|
24
|
+
<span class="mono">{{ m.sender }}</span>{% if m.sender_declared %} <span class="badge declared">self-declared</span>{% endif %}
|
|
25
|
+
<span class="muted">({{ m.sender_kind }}) → <span class="mono">{{ m.recipient }}</span></span>
|
|
26
|
+
<span class="badge msg-{{ m.kind }}">{{ m.kind }}</span>{% if m.kind == 'question' %} <span class="badge st-{{ m.status }}">{{ m.status }}</span>{% endif %}
|
|
27
|
+
<time data-rel datetime="{{ m.created_at }}">{{ m.created_at }}</time>
|
|
28
|
+
</header>
|
|
29
|
+
{% set agent = agents.get(m.agent_id) if m.agent_id else None %}
|
|
30
|
+
{% if agent and m.id == agent.spawn_msg_id %}
|
|
31
|
+
<p class="meta"><b>Agent started</b><a href="/messages/agent/{{ agent.agent_id }}">{{ agent.agent_type or 'subagent' }}</a>{% if agent.description %}<span>{{ agent.description }}</span>{% endif %}</p>
|
|
32
|
+
<details><summary>Task</summary><pre class="msg-body">{{ m.body }}</pre></details>
|
|
33
|
+
{{ tools(agent) }}
|
|
34
|
+
{% elif agent and m.id == agent.report_msg_id %}
|
|
35
|
+
<p><b>Final report</b></p>
|
|
36
|
+
<pre class="msg-body">{{ m.body }}</pre>
|
|
37
|
+
{% else %}
|
|
38
|
+
{% if m.subject and not loop.first %}<p><b>{{ m.subject }}</b></p>{% endif %}
|
|
39
|
+
<pre class="msg-body">{{ m.body }}</pre>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</article>
|
|
42
|
+
{% endfor %}
|
|
43
|
+
|
|
44
|
+
<form method="post" action="/messages/thread/{{ root.id }}/reply" class="card">
|
|
45
|
+
<label class="muted" for="reply-body">Reply as user; an agent waiting in <span class="mono">message_wait</span> gets it right away.</label>
|
|
46
|
+
<textarea id="reply-body" name="body" required maxlength="8000"></textarea>
|
|
47
|
+
<button>Send reply</button>
|
|
48
|
+
</form>
|
|
49
|
+
{% endblock %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Messages · Brain{% endblock %}
|
|
3
|
+
{% block body_class %}chat-body{% endblock %}
|
|
4
|
+
{% block main %}
|
|
5
|
+
<main class="live-chat" id="live-chat"><aside class="live-members"><h1>Messages</h1><p class="muted">One live chat · last 2 hours</p><div id="live-agents">{% for agent in agents %}<div class="live-agent" title="{{ agent.key }}">{% if agent.color %}<i class="agent-dot" style="--agent: {{ agent.color.hex }}"></i>{% endif %}<div class="live-agent-info"><span>{{ agent.name }}</span><small>{{ agent.status }}{% if agent.status == 'idle' %} · will see messages at its next action{% endif %}</small></div>{% if agent.connectable %}<button data-agent="{{ agent.key }}">{{ 'Disconnect' if agent.connected else 'Connect' }}</button>{% endif %}</div>{% endfor %}</div></aside><section class="live-pane"><div class="chat-scroll" id="live-scroll"><div class="chat-items" id="live-items">{% for item in messages %}<article class="bubble {{ 'mine' if item.sender_kind == 'user' else 'theirs' }}{% if item.color %} agent-bubble{% endif %}"{% if item.color %} style="--agent: {{ item.color.hex }}"{% endif %}>{% if item.color %}<div class="b-who"><i class="agent-dot"></i>{{ item.display_name }}</div>{% else %}<div class="b-who">{{ item.display_name }}</div>{% endif %}{% if item.kind == 'event' %}<div class="b-subject">{{ item.headline }}</div>{% endif %}<div class="b-text">{{ item.body }}</div><div class="b-foot">{{ item.created_at | live_time }}{% if item.kind == 'question' and item.status == 'open' %} · Question open{% endif %} · <a href="/messages/thread/{{ item.thread_id | urlencode }}">Open</a></div>{% if item.sender_kind == 'user' and item.recipient == '#live' %}<div class="delivery">{% for status in item.delivery %}<span>{{ 'Received by' if status.state == 'received' else 'Waiting for' }} {% if status.color %}<i class="agent-dot" style="--agent: {{ status.color.hex }}"></i>{% endif %}{{ status.name }}</span>{% else %}Not delivered{% endfor %}</div>{% endif %}</article>{% else %}<p class="muted">No messages in the last 2 hours.</p>{% endfor %}</div></div>{% if dispatch_enabled %}<label>Project directory <input name="project" form="live-form" value="{{ dispatch_project }}" aria-label="Project directory for automatic replies"></label>{% endif %}<form class="chat-input" id="live-form" action="/messages/live/send" method="post"><textarea id="live-body" name="body" rows="1" maxlength="8000" required placeholder="Message the connected agents"></textarea><button>Send</button></form><p class="muted live-note" id="live-note">Agents see messages at their next action. An idle agent waits until you type in its session.</p><p class="chat-error" id="live-error" hidden></p></section></main>
|
|
6
|
+
<script id="live-data" type="application/json">{{ {'messages': messages, 'agents': agents, 'connected': connected} | tojson }}</script>
|
|
7
|
+
{% endblock %}
|
|
8
|
+
{% block scripts %}<script src="{{ asset('chat.js') }}"></script>{% endblock %}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Overview · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Overview</h1>
|
|
5
|
+
<p class="muted">Token figures come from local logs. Limits come from Codex logs and the Claude status line snapshot.</p>
|
|
6
|
+
|
|
7
|
+
<div class="grid-cards">
|
|
8
|
+
{% for x in totals %}
|
|
9
|
+
<div class="card">
|
|
10
|
+
<span class="badge {{ x.agent }}">{{ x.agent }}</span>
|
|
11
|
+
<div class="stat-label stat-gap">Today</div>
|
|
12
|
+
<div class="stat-value" data-tokens="{{ x.today or 0 }}">{{ x.today or 0 }}</div>
|
|
13
|
+
<div class="muted meta"><span>7d <span class="mono" data-tokens="{{ x.week or 0 }}">{{ x.week or 0 }}</span></span>
|
|
14
|
+
<span>30d <span class="mono" data-tokens="{{ x.month or 0 }}">{{ x.month or 0 }}</span></span></div>
|
|
15
|
+
</div>
|
|
16
|
+
{% endfor %}
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="card panel-tokens" id="tokens">
|
|
20
|
+
<div class="tok-block">
|
|
21
|
+
<h3>Today</h3>
|
|
22
|
+
<div class="tok-big" id="tok-today">0</div>
|
|
23
|
+
<div class="tok-split" id="tok-today-split"></div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="tok-block">
|
|
26
|
+
<h3>7 days</h3>
|
|
27
|
+
<div class="tok-big" id="tok-week">0</div>
|
|
28
|
+
<div class="tok-split" id="tok-week-split"></div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="tok-block tok-spark">
|
|
31
|
+
<h3>Last 24 h <span class="hint">tokens / hour</span></h3>
|
|
32
|
+
<svg id="spark" preserveAspectRatio="none" role="img" aria-label="Tokens per hour, last 24 hours"></svg>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="tok-block tok-models">
|
|
35
|
+
<h3>Models today
|
|
36
|
+
<span class="hint seg-legend"><span><span class="sw s-in"></span>in</span> <span><span class="sw s-out"></span>out</span> <span><span class="sw s-cr"></span>cache r</span> <span><span class="sw s-cw"></span>cache w</span></span>
|
|
37
|
+
</h3>
|
|
38
|
+
<ul id="models"></ul>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{% for board in boards %}
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h2><a href="/memory/{{ board.id }}">{{ board.title }}</a></h2>
|
|
45
|
+
{% if board.status_as_of %}<p class="muted">Status as of {{ board.status_as_of }}.</p>{% endif %}
|
|
46
|
+
<table>
|
|
47
|
+
<thead><tr><th>Section</th><th>#</th><th>Task</th><th>Title</th><th>Phase</th></tr></thead>
|
|
48
|
+
<tbody>
|
|
49
|
+
{% for entry in board.entries %}
|
|
50
|
+
<tr><td><span class="badge">{{ entry.section }}</span></td><td class="mono">{{ entry.n }}</td><td class="mono">{{ entry.task }}</td><td>{{ entry.title }}</td><td>{{ entry.phase }}</td></tr>
|
|
51
|
+
{% else %}
|
|
52
|
+
<tr><td colspan="5" class="muted">No task entries.</td></tr>
|
|
53
|
+
{% endfor %}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
</div>
|
|
57
|
+
{% endfor %}
|
|
58
|
+
|
|
59
|
+
<h2>Daily tokens, last 30 days</h2>
|
|
60
|
+
<div class="card"><svg id="chart" class="chart" role="img" aria-label="Daily tokens per agent, last 30 days"></svg></div>
|
|
61
|
+
|
|
62
|
+
<h2>Limits</h2>
|
|
63
|
+
<table>
|
|
64
|
+
<thead><tr><th>Agent</th><th>Window</th><th class="num">Used</th><th>Resets</th><th>As of</th></tr></thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
{% for x in limits %}
|
|
67
|
+
<tr>
|
|
68
|
+
<td><span class="badge {{ x.agent }}">{{ x.agent }}</span></td>
|
|
69
|
+
<td title="{{ x.label }}{% if x.window_minutes %}, {{ x.window_minutes }} min{% endif %}">{{ x | window_label }}</td>
|
|
70
|
+
<td class="num">{{ x.used_percent }}%</td>
|
|
71
|
+
<td class="mono">{% if x.resets_at %}<time datetime="{{ x.resets_at }}" title="{{ x.resets_at }}">{{ x.resets_at | when }}</time>{% else %}—{% endif %}</td>
|
|
72
|
+
<td><time data-rel datetime="{{ x.as_of }}">{{ x.as_of }}</time>{% if x.stale %} <span class="badge">stale</span>{% endif %}</td>
|
|
73
|
+
</tr>
|
|
74
|
+
{% else %}
|
|
75
|
+
<tr><td colspan="5" class="muted">No limit data yet.</td></tr>
|
|
76
|
+
{% endfor %}
|
|
77
|
+
</tbody>
|
|
78
|
+
</table>
|
|
79
|
+
|
|
80
|
+
<h2>Top projects by tokens</h2>
|
|
81
|
+
<table>
|
|
82
|
+
<thead><tr><th>Project</th><th class="num">Tokens</th></tr></thead>
|
|
83
|
+
<tbody>
|
|
84
|
+
{% for x in projects %}
|
|
85
|
+
<tr>
|
|
86
|
+
<td class="mono">{{ x.project }}</td>
|
|
87
|
+
<td class="num" data-tokens="{{ x.tokens }}">{{ x.tokens }}</td>
|
|
88
|
+
</tr>
|
|
89
|
+
{% endfor %}
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
{% endblock %}
|
|
93
|
+
{% block scripts %}
|
|
94
|
+
<script>
|
|
95
|
+
(function () {
|
|
96
|
+
const d = {{ daily | tojson }};
|
|
97
|
+
const days = [...new Set(d.map(x => x.day))];
|
|
98
|
+
const rows = days.map(day => {
|
|
99
|
+
const series = {};
|
|
100
|
+
d.filter(x => x.day === day).forEach(x => { series[x.agent] = x.tokens || 0; });
|
|
101
|
+
return { x: day, series };
|
|
102
|
+
});
|
|
103
|
+
devmem.barChart(document.querySelector('#chart'), rows, devmem.agentColors);
|
|
104
|
+
const tokens = {{ tokens | tojson }};
|
|
105
|
+
devmem.renderTokens(tokens);
|
|
106
|
+
window.addEventListener('resize', () => devmem.renderSpark(tokens.hourly || {}));
|
|
107
|
+
})();
|
|
108
|
+
</script>
|
|
109
|
+
{% endblock %}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Project · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1 class="mono">{{ project }}</h1>
|
|
5
|
+
|
|
6
|
+
<h2>Memories</h2>
|
|
7
|
+
{% for x in memories %}
|
|
8
|
+
<div class="card">
|
|
9
|
+
<span class="badge kind-{{ x.kind }}">{{ x.kind }}</span>
|
|
10
|
+
<b>{{ x.title }}</b>
|
|
11
|
+
<div class="muted memory-body">{{ x.content }}</div>
|
|
12
|
+
</div>
|
|
13
|
+
{% else %}
|
|
14
|
+
<p class="muted">No memories for this project.</p>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
|
|
17
|
+
<h2>Recent activity</h2>
|
|
18
|
+
<table>
|
|
19
|
+
<thead><tr><th>When</th><th>Agent</th><th>Tool</th><th>Summary</th></tr></thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
{% for x in activity %}
|
|
22
|
+
<tr>
|
|
23
|
+
<td><time data-rel datetime="{{ x.ts }}">{{ x.ts }}</time></td>
|
|
24
|
+
<td><span class="badge {{ x.agent }}">{{ x.agent }}</span></td>
|
|
25
|
+
<td>{{ x.name }}</td>
|
|
26
|
+
<td class="muted">{{ x.summary }}</td>
|
|
27
|
+
</tr>
|
|
28
|
+
{% else %}
|
|
29
|
+
<tr><td colspan="4" class="muted">No activity recorded.</td></tr>
|
|
30
|
+
{% endfor %}
|
|
31
|
+
</tbody>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
34
|
+
<h2>Usage events</h2>
|
|
35
|
+
<table>
|
|
36
|
+
<thead><tr><th>When</th><th>Agent</th><th>Model</th><th class="num">Tokens</th></tr></thead>
|
|
37
|
+
<tbody>
|
|
38
|
+
{% for x in events %}
|
|
39
|
+
<tr>
|
|
40
|
+
<td><time data-rel datetime="{{ x.ts }}">{{ x.ts }}</time></td>
|
|
41
|
+
<td><span class="badge {{ x.agent }}">{{ x.agent }}</span></td>
|
|
42
|
+
<td class="muted">{{ x.model }}</td>
|
|
43
|
+
<td class="num">{{ x.input_tokens + x.output_tokens + x.cache_read_tokens + x.cache_write_tokens }}</td>
|
|
44
|
+
</tr>
|
|
45
|
+
{% endfor %}
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
{% endblock %}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Projects · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Projects</h1>
|
|
5
|
+
<table>
|
|
6
|
+
<thead>
|
|
7
|
+
<tr><th>Project</th><th class="num">Tokens</th><th class="num">Sessions</th><th class="num">Memories</th><th>Last activity</th></tr>
|
|
8
|
+
</thead>
|
|
9
|
+
<tbody>
|
|
10
|
+
{% for x in rows %}
|
|
11
|
+
<tr>
|
|
12
|
+
<td><a class="mono" href="/projects/detail?path={{ x.project | urlencode }}">{{ x.project }}</a></td>
|
|
13
|
+
<td class="num" data-tokens="{{ x.tokens }}">{{ x.tokens }}</td>
|
|
14
|
+
<td class="num">{{ x.sessions }}</td>
|
|
15
|
+
<td class="num">{{ x.memories }}</td>
|
|
16
|
+
<td><time data-rel datetime="{{ x.activity }}">{{ x.activity }}</time></td>
|
|
17
|
+
</tr>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</tbody>
|
|
20
|
+
</table>
|
|
21
|
+
{% endblock %}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Session · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Session <span class="mono">{{ session_id }}</span></h1>
|
|
5
|
+
<div class="card"><svg id="timeline" class="chart" role="img" aria-label="Tokens per usage event"></svg></div>
|
|
6
|
+
|
|
7
|
+
<h2>Tool activity</h2>
|
|
8
|
+
<table>
|
|
9
|
+
<thead><tr><th>When</th><th>Agent</th><th>Tool</th><th>Summary</th></tr></thead>
|
|
10
|
+
<tbody>
|
|
11
|
+
{% for x in activity %}
|
|
12
|
+
<tr>
|
|
13
|
+
<td><time data-rel datetime="{{ x.ts }}">{{ x.ts }}</time></td>
|
|
14
|
+
<td><span class="badge {{ x.agent }}">{{ x.agent }}</span></td>
|
|
15
|
+
<td>{{ x.name }}</td>
|
|
16
|
+
<td class="muted">{{ x.summary }}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
{% else %}
|
|
19
|
+
<tr><td colspan="4" class="muted">No activity recorded.</td></tr>
|
|
20
|
+
{% endfor %}
|
|
21
|
+
</tbody>
|
|
22
|
+
</table>
|
|
23
|
+
|
|
24
|
+
<h2>Usage events</h2>
|
|
25
|
+
<table>
|
|
26
|
+
<thead><tr><th>When</th><th class="num">Input</th><th class="num">Output</th><th class="num">Cache read</th><th class="num">Cache write</th></tr></thead>
|
|
27
|
+
<tbody>
|
|
28
|
+
{% for x in events %}
|
|
29
|
+
<tr>
|
|
30
|
+
<td class="mono"><time datetime="{{ x.ts }}" title="{{ x.ts }}">{{ x.ts | when(True) }}</time></td>
|
|
31
|
+
<td class="num">{{ x.input_tokens }}</td>
|
|
32
|
+
<td class="num">{{ x.output_tokens }}</td>
|
|
33
|
+
<td class="num">{{ x.cache_read_tokens }}</td>
|
|
34
|
+
<td class="num">{{ x.cache_write_tokens }}</td>
|
|
35
|
+
</tr>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
{% endblock %}
|
|
40
|
+
{% block scripts %}
|
|
41
|
+
<script>
|
|
42
|
+
(function () {
|
|
43
|
+
const e = {{ events | tojson }};
|
|
44
|
+
const rows = e.slice(-120).map(x => ({ x: x.ts, series: {
|
|
45
|
+
input: x.input_tokens, output: x.output_tokens, cache: x.cache_read_tokens + x.cache_write_tokens } }));
|
|
46
|
+
devmem.barChart(document.querySelector('#timeline'), rows, { input: '#cecac3', output: '#6db0f4', cache: '#403d37' });
|
|
47
|
+
})();
|
|
48
|
+
</script>
|
|
49
|
+
{% endblock %}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}Sessions · Brain{% endblock %}
|
|
3
|
+
{% block body %}
|
|
4
|
+
<h1>Sessions</h1>
|
|
5
|
+
<table>
|
|
6
|
+
<thead>
|
|
7
|
+
<tr><th>Agent</th><th>Project</th><th>Model</th><th>Start → end</th><th class="num">Turns</th><th class="num">Input / output / cache</th></tr>
|
|
8
|
+
</thead>
|
|
9
|
+
<tbody>
|
|
10
|
+
{% for x in rows %}
|
|
11
|
+
<tr data-start="{{ x.start }}" data-end="{{ x.end }}" data-input="{{ x.input or 0 }}" data-output="{{ x.output or 0 }}" data-cache="{{ x.cache or 0 }}" data-agent="{{ x.agent }}">
|
|
12
|
+
<td><span class="badge {{ x.agent }}">{{ x.agent }}</span></td>
|
|
13
|
+
<td class="mono">{{ x.project }}</td>
|
|
14
|
+
<td class="muted">{{ x.model }}</td>
|
|
15
|
+
<td><a class="span" href="/sessions/{{ x.session_id }}">{{ x.start }} → {{ x.end }}</a></td>
|
|
16
|
+
<td class="num">{{ x.turns }}</td>
|
|
17
|
+
<td class="num"><span data-tokens="{{ x.input }}">{{ x.input }}</span> /
|
|
18
|
+
<span data-tokens="{{ x.output }}">{{ x.output }}</span> /
|
|
19
|
+
<span data-tokens="{{ x.cache }}">{{ x.cache }}</span></td>
|
|
20
|
+
</tr>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
{% endblock %}
|
|
25
|
+
{% block scripts %}
|
|
26
|
+
<script>
|
|
27
|
+
// split rows into local-time days (by last activity), show local HH:MM and per-day token totals
|
|
28
|
+
(function () {
|
|
29
|
+
var tbody = document.querySelector("tbody"), header = null, sum = null;
|
|
30
|
+
var today = new Date().toDateString(), yday = new Date(Date.now() - 864e5).toDateString();
|
|
31
|
+
function hm(d) { return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }
|
|
32
|
+
function closeDay() {
|
|
33
|
+
if (!header) return;
|
|
34
|
+
var cell = header.lastChild, total = sum.input + sum.output + sum.cache;
|
|
35
|
+
var parts = Object.keys(sum.agents).sort(function (a, b) { return sum.agents[b] - sum.agents[a]; })
|
|
36
|
+
.map(function (a) { return a + " " + devmem.fmtTokens(sum.agents[a]); });
|
|
37
|
+
cell.textContent = devmem.fmtTokens(total) + " tokens" + (parts.length ? " · " + parts.join(" · ") : "");
|
|
38
|
+
cell.title = "input " + sum.input + " / output " + sum.output + " / cache " + sum.cache;
|
|
39
|
+
}
|
|
40
|
+
Array.prototype.slice.call(tbody.rows).forEach(function (tr) {
|
|
41
|
+
var s = new Date(tr.dataset.start), e = new Date(tr.dataset.end);
|
|
42
|
+
if (isNaN(e)) return;
|
|
43
|
+
var day = e.toDateString();
|
|
44
|
+
if (!header || day !== header.dataset.day) {
|
|
45
|
+
closeDay();
|
|
46
|
+
var label = day === today ? "Today" : day === yday ? "Yesterday" : "";
|
|
47
|
+
var date = e.toLocaleDateString([], { weekday: "short", day: "numeric", month: "short", year: "numeric" });
|
|
48
|
+
header = document.createElement("tr");
|
|
49
|
+
header.className = "day";
|
|
50
|
+
header.dataset.day = day;
|
|
51
|
+
header.innerHTML = '<th colspan="4"></th><th class="num" colspan="2"></th>';
|
|
52
|
+
header.firstChild.textContent = label ? label + " · " + date : date;
|
|
53
|
+
tbody.insertBefore(header, tr);
|
|
54
|
+
sum = { input: 0, output: 0, cache: 0, agents: {} };
|
|
55
|
+
}
|
|
56
|
+
sum.input += +tr.dataset.input || 0;
|
|
57
|
+
sum.output += +tr.dataset.output || 0;
|
|
58
|
+
sum.cache += +tr.dataset.cache || 0;
|
|
59
|
+
var agent = tr.dataset.agent || "other";
|
|
60
|
+
sum.agents[agent] = (sum.agents[agent] || 0) + (+tr.dataset.input || 0) + (+tr.dataset.output || 0) + (+tr.dataset.cache || 0);
|
|
61
|
+
var startTxt = isNaN(s) ? "?" : (s.toDateString() === day ? "" : s.toLocaleDateString([], { day: "numeric", month: "short" }) + " ") + hm(s);
|
|
62
|
+
tr.querySelector("a.span").textContent = startTxt + " → " + hm(e);
|
|
63
|
+
});
|
|
64
|
+
closeDay();
|
|
65
|
+
})();
|
|
66
|
+
</script>
|
|
67
|
+
{% endblock %}
|