-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrows.ts
More file actions
84 lines (73 loc) · 3.27 KB
/
Copy pathrows.ts
File metadata and controls
84 lines (73 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// The sidebar's layout unit: labelled rows, one figure per line.
//
// Pure, like history.ts and panels.ts. Adapters and the universal layer build
// a TurnView; the entry file draws it into a box. A value with parts
// continues on the next row under an empty label, so every row fits the
// sidebar's 38 columns: a 12-cell label column inside a box with a 1-cell
// margin and 2-cell side padding leaves 20 cells for the value.
/** A label and its value. An empty label continues the row above. */
export type Row = readonly [label: string, value: string]
export interface TurnView {
/** The heading: the engine (or provider id), never the model. */
engine: string
rows: Row[]
/** Full-width notes below the rows, e.g. why engine figures are missing. */
notes: string[]
/** The one figure a collapsed heading keeps, e.g. `34.4 tok/s`. */
key?: string
/**
* For the details dialog: every figure the engine itself measured, and
* nothing of OpenCode's. Absent on a view drawn from OpenCode's figures.
*/
detail?: Row[]
}
/** Width of the label column, in cells. */
export const LABEL_WIDTH = 12
/** Numbers with thousands separators, as the mockup shows them. */
export const nt = (v: unknown): string =>
typeof v === "number" && isFinite(v) ? Math.round(v).toLocaleString("en-US") : "?"
/** A row per part: the first carries the label, the rest continue it. */
export function rowsOf(label: string, parts: readonly string[]): Row[] {
return parts.filter(Boolean).map((p, i) => [i === 0 ? label : "", p] as const)
}
/** One line per row, label padded to its column. */
export function rowLines(rows: readonly Row[]): string[] {
return rows.map(([label, value]) => `${label.padEnd(LABEL_WIDTH)}${value}`)
}
/**
* A view as plain text: the engine, then `label value` per row, then notes.
* Used to store a view per session and by tests that look for a figure.
*/
export function viewText(v: TurnView): string {
return [v.engine, ...v.rows.map(([l, val]) => (l ? `${l} ${val}` : val)), ...v.notes].join("\n")
}
/** Serialised for the per-session store, which holds strings. */
export function encodeView(v: TurnView): string {
return JSON.stringify(v)
}
/** The stored string back to a view; a legacy plain-text line becomes a note. */
export function decodeView(s: string): TurnView {
try {
const v = JSON.parse(s) as TurnView
if (v && typeof v.engine === "string" && Array.isArray(v.rows)) return { ...v, notes: v.notes ?? [] }
} catch {
// not JSON: a placeholder or a line stored by an earlier version
}
const [engine = "", ...rest] = s.split("\n")
return { engine, rows: [], notes: rest }
}
/** A phase's tokens and time, e.g. `7,907 tok · 17.19s`. */
export const phase = (tokens: number | undefined, seconds: number | undefined): string =>
[tokens !== undefined ? `${nt(tokens)} tok` : "", seconds !== undefined && isFinite(seconds) ? `${seconds.toFixed(2)}s` : ""]
.filter(Boolean)
.join(" · ")
/**
* The turn's total -- what the user waited, from OpenCode -- and any
* retries on the row below it.
*/
export function timeRows(total: number | undefined, retries = 0): Row[] {
return rowsOf("time", [
total !== undefined && isFinite(total) ? `${total.toFixed(2)}s` : "",
retries > 0 ? `${retries} ${retries === 1 ? "retry" : "retries"}` : "",
])
}