feat: color requests by thread id

This commit is contained in:
2026-07-09 08:43:32 +08:00
parent a97ac87367
commit 58638d9fe3
2 changed files with 92 additions and 1 deletions
+47 -1
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import argparse import argparse
import datetime as dt import datetime as dt
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import hashlib
import importlib.metadata import importlib.metadata
import json import json
import os import os
@@ -81,6 +82,19 @@ REQUEST_TABLE_COLUMN_KEY_SET = set(REQUEST_TABLE_COLUMN_KEYS)
REQUEST_TABLE_CELL_INDEX = {key: index for index, key in enumerate(REQUEST_TABLE_COLUMN_KEYS)} REQUEST_TABLE_CELL_INDEX = {key: index for index, key in enumerate(REQUEST_TABLE_COLUMN_KEYS)}
DEFAULT_REQUEST_TABLE_VISIBLE_COLUMNS = REQUEST_TABLE_COLUMN_KEYS DEFAULT_REQUEST_TABLE_VISIBLE_COLUMNS = REQUEST_TABLE_COLUMN_KEYS
DEFAULT_REQUEST_TABLE_SORT_COLUMN = "seq" DEFAULT_REQUEST_TABLE_SORT_COLUMN = "seq"
THREAD_COLOR_PALETTE = (
"#5fd7ff",
"#87d75f",
"#ffaf5f",
"#d787ff",
"#5fafff",
"#ff87d7",
"#d7af5f",
"#5fd7af",
"#af87ff",
"#87afd7",
)
THREAD_COLOR_COLUMN_KEYS = frozenset({"req_id", "resp_id", "thread"})
REQUEST_TABLE_WIDTH_PROFILES = { REQUEST_TABLE_WIDTH_PROFILES = {
"compact": { "compact": {
"seq": 7, "seq": 7,
@@ -990,6 +1004,37 @@ def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None =
) )
def request_thread_color(thread_id: Any) -> str | None:
text = str(thread_id or "").strip()
if not text:
return None
digest = hashlib.blake2b(text.encode("utf-8"), digest_size=4).digest()
color_index = int.from_bytes(digest[:2], "big") % len(THREAD_COLOR_PALETTE)
return THREAD_COLOR_PALETTE[color_index]
def request_visible_cells(
row: dict[str, Any],
cells: tuple[Any, ...],
visible_column_keys: list[str],
) -> list[Any]:
visible_cells = [cells[REQUEST_TABLE_CELL_INDEX[column_key]] for column_key in visible_column_keys]
thread_color = request_thread_color(row.get("thread_id"))
if not thread_color:
return visible_cells
try:
from rich.text import Text
except ImportError:
return visible_cells
styled_cells: list[Any] = []
for column_key, cell in zip(visible_column_keys, visible_cells):
if column_key in THREAD_COLOR_COLUMN_KEYS:
styled_cells.append(Text(str(cell), style=f"bold {thread_color}"))
else:
styled_cells.append(cell)
return styled_cells
def request_table_column_label(column_key: str) -> str: def request_table_column_label(column_key: str) -> str:
column = REQUEST_TABLE_COLUMN_BY_KEY.get(column_key) column = REQUEST_TABLE_COLUMN_BY_KEY.get(column_key)
return str(column["label"]) if column else column_key return str(column["label"]) if column else column_key
@@ -2113,13 +2158,14 @@ def run_textual(
selected_row_index = table.cursor_row if table.cursor_row is not None and table.cursor_row >= 0 else 0 selected_row_index = table.cursor_row if table.cursor_row is not None and table.cursor_row >= 0 else 0
selected_row_key = self._selected_request_key() selected_row_key = self._selected_request_key()
text_limits = self._request_text_limits(table) text_limits = self._request_text_limits(table)
visible_column_keys = self._visible_request_column_keys()
table.clear() table.clear()
self.request_by_key = {} self.request_by_key = {}
for row in self.requests: for row in self.requests:
key = self._request_row_key(row) key = self._request_row_key(row)
self.request_by_key[key] = row self.request_by_key[key] = row
cells = request_row_cells(row, text_limits) cells = request_row_cells(row, text_limits)
visible_cells = [cells[REQUEST_TABLE_CELL_INDEX[column_key]] for column_key in self._visible_request_column_keys()] visible_cells = request_visible_cells(row, cells, visible_column_keys)
table.add_row(*visible_cells, key=key) table.add_row(*visible_cells, key=key)
if self.requests: if self.requests:
if not self._select_request_row(selected_row_key): if not self._select_request_row(selected_row_key):
+45
View File
@@ -263,6 +263,51 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
self.assertIn("current wave round 3 slot 1 first 1.2s", mod.render_request_detail(row)) self.assertIn("current wave round 3 slot 1 first 1.2s", mod.render_request_detail(row))
self.assertEqual(mod.request_row_cells(row)[20], "3(2)") self.assertEqual(mod.request_row_cells(row)[20], "3(2)")
def test_request_visible_cells_colorize_thread_identity_columns(self) -> None:
mod = load_module()
try:
from rich.text import Text
except ImportError:
self.skipTest("rich is not installed")
row = {
"seq": 1,
"request_id": "req_1",
"response_id": "resp_1",
"thread_id": "thread_same",
"started_at": "2026-06-30T12:00:00Z",
"status_code": 200,
"path": "/responses",
"model": "gpt-5",
"requested_model": "",
"forwarded_model": "",
"reasoning_tokens": 7,
"reasoning_effort": "xhigh",
"input_tokens": 20,
"output_tokens": 10,
"cached_tokens": 5,
"request_body_bytes": 1536,
"response_bytes_received": 4096,
"response_stream": True,
"stream_chunk_count": 4,
"first_response_delay_ms": 1200,
"duration_ms": 3400,
"upstream_attempt_count": 1,
"error": "",
"finished_at": "2026-06-30T12:00:03Z",
}
cells = mod.request_row_cells(row)
visible = mod.request_visible_cells(row, cells, ["seq", "req_id", "resp_id", "thread", "status"])
self.assertEqual(visible[0], "1")
self.assertIsInstance(visible[1], Text)
self.assertIsInstance(visible[2], Text)
self.assertIsInstance(visible[3], Text)
self.assertEqual(visible[1].plain, "req_1")
self.assertEqual(visible[2].plain, "resp_1")
self.assertEqual(visible[3].plain, "thread_same")
self.assertEqual(visible[4], "· 200")
self.assertEqual(mod.request_thread_color("thread_same"), mod.request_thread_color("thread_same"))
self.assertIsNone(mod.request_thread_color(""))
def test_request_table_preferences_round_trip(self) -> None: def test_request_table_preferences_round_trip(self) -> None:
mod = load_module() mod = load_module()
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir: