// Builds docs/figs/agent-team-activity.svg — a self-contained reproduction of
// the dsh-agent-teams live activity panel (the picture in its README:
// https://github.com/NanmiCoder/dsh-agent-teams/blob/main/assets/ui.png),
// adapted to the RigorQuant research team.
//
// The role portraits from docs/figs/ are embedded as base64 data URIs so
// the one SVG file renders anywhere (GitHub, local browser) with no external
// references. To regenerate:
//
// node docs/figs/agent-team-activity.js
//
// Design credit: activity-panel design adapted from dsh-agent-teams
// © NanmiCoder (程序员阿江 / Relakkes), MIT License. Portraits © RigorQuant
// docs/figs.
//
// No build step, no deps: plain Node string assembly. ESM on purpose — the
// repository is "type": "module"; run it with `node docs/figs/agent-team-activity.js`.
import fs from 'node:fs'
import path from 'node:path'
const HERE = path.dirname(new URL(import.meta.url).pathname)
const FIG = (name) => path.join(HERE, name)
const PNG = (name) => fs.readFileSync(FIG(name)).toString('base64')
// ---------------------------------------------------------------- palette
const C = {
panel: '#0B1626',
border: '#20334C',
borderL2: '#2B4567',
text: '#F4F8FF',
text2: '#AFC4E0',
text3: '#7F98BB',
blue: '#4F8CFF',
green: '#35D5A4',
amber: '#F2BD62',
node: '#101F33',
track: '#14273F',
pill: '#102642',
pillBorder: '#315D91',
pillText: '#BBD5FF',
quota: '#5E7595',
mono: 'Menlo, Consolas, monospace',
sans: 'Helvetica Neue, Arial, sans-serif',
}
const esc = (text) => text
.replace(/&/g, '&')
.replace(//g, '>')
/**
* An avatar portrait cropped to its character. The doc PNGs carry the role
* name/subtitle at their bottom edge; the image is drawn at its true aspect
* ratio (square) inside a shorter clip box, so that label is cut off.
*/
function avatar(id, x, y, w, h, rx, png) {
return [
``,
``,
].join('\n')
}
/** One member roster row starting at (16, top); the row is 50px tall. */
function memberRow(id, top, m) {
const nameX = 84
const nameY = top + 17
const statusY = top + 33
const toolX = nameX + 9 + 7.1 * m.name.length + 7
return `
${avatar(id, 16, top, 56, 42, 8, PNG(m.png))}
${esc(m.name)}
· ${esc(m.tool)}
${esc(m.status)}
${esc(m.tag)}
0/1
`
}
/** One task-DAG node: mono TASK label + bold title. */
function dagNode(x, y, label, title, accent, titleFill) {
return `
${esc(label)}
${esc(title)}
`
}
const arrow = (d) =>
``
// -------------------------------------------------------- vertical layout
const HEADER_H = 45
const TEAM_H = 106
const PROG_H = 72
const MEMBER_ROW = 50
const membersHeadY = HEADER_H + TEAM_H + PROG_H + 6 // head text baseline
const membersTop = membersHeadY + 26 // first row avatar top
const MEMBERS = [
{ png: 'avatar-explorer.png', name: 'Explorer', tool: 'subagent_explorer', status: 'working · t2 — candidate methods, parallel batches', tag: 't2', busy: true },
{ png: 'avatar-offgrid.png', name: 'OffGridThinker', tool: 'subagent_offgrid', status: 'working · t2 — off-grid derivation, compute only', tag: 't2', busy: true },
{ png: 'avatar-literature.png', name: 'Literature', tool: 'subagent_lit_line', status: 'working · t2 — citation-graph sweep, both ways', tag: 't2', busy: true },
{ png: 'avatar-doublechecker.png', name: 'DoubleChecker', tool: 'subagent_double_checker', status: 'on standby · t3 — blind re-derivation, two ways', tag: 't3', busy: false },
{ png: 'avatar-adversary.png', name: 'Adversary', tool: 'subagent_adversary', status: 'on standby · t4 — battery + counterexamples', tag: 't4', busy: false },
{ png: 'avatar-document-adversary.png', name: 'Document', tool: 'subagent_document_adversary', status: 'on standby · t5 — self-completeness audit', tag: 't5', busy: false },
{ png: 'avatar-validator.png', name: 'Validator', tool: 'rq_check.py', status: 'on standby · t5 — evidence gate / PASS', tag: 't5', busy: false },
]
const membersSvg = MEMBERS.map((m, i) => {
const row = membersTop + i * MEMBER_ROW
const hairline = i === 0
? ''
: ``
return hairline + memberRow(`ap${i}`, row, m)
}).join('\n')
const membersBottom = membersTop + MEMBERS.length * MEMBER_ROW
const dagHeadY = membersBottom + 14 // head text baseline
const dagTop = dagHeadY + 10 // svg top (nodes already padded inside)
const DAG_SVG_H = 172
const noteTop = dagTop + DAG_SVG_H + 8
const NOTE_H = 36
const footerTop = noteTop + NOTE_H + 10
const PANEL_H = footerTop + 42
const svg = `
`
fs.writeFileSync(FIG('agent-team-activity.svg'), `
${svg}
`)
console.log(`wrote agent-team-activity.svg (${svg.length} bytes, panel ${PANEL_H}px tall)`)