mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 04:37:44 +02:00
ui/ux: hide noisy memory meter, surface accurate post-compact tokens
Drop the inline 'Memory N% full' pill from AgentChat and the mode label from AgentCard so the chat header stays quiet until tools are connected. Backend now emits agent:context_update after compact and clear so the session token counters reflect the trimmed history instead of stale pre-compact totals, with a new _estimate_post_compact_input helper and matching invariants in test_v2_invariants.
This commit is contained in:
@@ -53,6 +53,7 @@ from backend.apps.agents.manager.prompt.tool_catalog import (
|
||||
from backend.apps.agents.core.aux_llm import _safe_resp_text, clean_short_label
|
||||
from backend.apps.agents.manager.session.history_compaction import (
|
||||
_build_history_prefix,
|
||||
_estimate_post_compact_input,
|
||||
_get_branch_messages,
|
||||
_truncate_large_tool_result,
|
||||
)
|
||||
@@ -377,6 +378,35 @@ class AgentManager:
|
||||
session.compacted_through_msg_id = last_id
|
||||
return True
|
||||
|
||||
async def _emit_context_update(
|
||||
self,
|
||||
session_id: str,
|
||||
session: AgentSession,
|
||||
*,
|
||||
input_tokens: int | None = None,
|
||||
output_tokens: int | None = None,
|
||||
cache_read_tokens: int = 0,
|
||||
cache_read_pct: float = 0.0,
|
||||
) -> None:
|
||||
if input_tokens is None:
|
||||
input_tokens = int(session.tokens.get("input", 0) or 0)
|
||||
if output_tokens is None:
|
||||
output_tokens = int(session.tokens.get("output", 0) or 0)
|
||||
session.tokens["input"] = input_tokens
|
||||
session.tokens["output"] = output_tokens
|
||||
ctx_window = max(1, getattr(session, "context_window", 0) or 200_000)
|
||||
await ws_manager.send_to_session(session_id, "agent:context_update", {
|
||||
"session_id": session_id,
|
||||
"input_tokens": input_tokens,
|
||||
"output_tokens": output_tokens,
|
||||
"cache_read_tokens": cache_read_tokens,
|
||||
"cache_read_pct": cache_read_pct,
|
||||
"ctx_used_pct": round(input_tokens / ctx_window, 4) if input_tokens else 0.0,
|
||||
"context_window": ctx_window,
|
||||
"framework_overhead_tokens": session.framework_overhead_tokens,
|
||||
"active_mcps": list(session.active_mcps),
|
||||
})
|
||||
|
||||
def _build_prompt_content(self, prompt: str, images: list | None = None, context_paths: list | None = None, forced_tools: list[str] | None = None, attached_skills: list | None = None, api_type: str = "anthropic", model: str = ""):
|
||||
return _build_prompt_content(prompt, images, context_paths, forced_tools, attached_skills, api_type, model)
|
||||
|
||||
@@ -1813,11 +1843,18 @@ class AgentManager:
|
||||
# zero latency on the user's turn.
|
||||
try:
|
||||
if self._maybe_compact(session):
|
||||
new_input = _estimate_post_compact_input(session)
|
||||
await ws_manager.send_to_session(session_id, "agent:context_status", {
|
||||
"session_id": session_id,
|
||||
"reason": "compacted",
|
||||
"compacted_through_msg_id": session.compacted_through_msg_id,
|
||||
})
|
||||
await self._emit_context_update(
|
||||
session_id,
|
||||
session,
|
||||
input_tokens=new_input,
|
||||
output_tokens=session.tokens.get("output", 0),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("compaction failed; proceeding without it")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from backend.config.Apps import SubApp
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
from backend.apps.agents.core.ws_manager import ws_manager
|
||||
from backend.apps.agents.core.models import AgentConfig, ApprovalResponse
|
||||
from backend.apps.agents.manager.session.history_compaction import _estimate_post_compact_input
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import WebSocket, WebSocketDisconnect, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
@@ -304,6 +305,12 @@ async def compact_session(session_id: str):
|
||||
"reason": "compacted",
|
||||
"compacted_through_msg_id": session.compacted_through_msg_id,
|
||||
})
|
||||
await agent_manager._emit_context_update(
|
||||
session_id,
|
||||
session,
|
||||
input_tokens=_estimate_post_compact_input(session),
|
||||
output_tokens=session.tokens.get("output", 0),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": True, "compacted": fired}
|
||||
@@ -329,6 +336,12 @@ async def clear_session(session_id: str):
|
||||
"status": session.status,
|
||||
"session": session.model_dump(mode="json"),
|
||||
})
|
||||
await agent_manager._emit_context_update(
|
||||
session_id,
|
||||
session,
|
||||
input_tokens=0,
|
||||
output_tokens=0,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": True}
|
||||
@@ -817,4 +830,3 @@ async def subscriptions_disconnect(body: dict):
|
||||
return {"ok": False, "error": "Connection not found"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@@ -71,6 +71,39 @@ def _build_history_prefix(messages, cutoff_msg_id: str | None = None) -> str:
|
||||
return "<prior_conversation>\n" + "\n".join(lines) + "\n</prior_conversation>"
|
||||
|
||||
|
||||
def _estimate_post_compact_input(session) -> int:
|
||||
"""Return a conservative token estimate after compaction trims history."""
|
||||
try:
|
||||
messages = _get_branch_messages(session)
|
||||
cutoff_msg_id = getattr(session, "compacted_through_msg_id", None)
|
||||
if cutoff_msg_id:
|
||||
skip_idx = next(
|
||||
(i for i, m in enumerate(messages) if m.id == cutoff_msg_id),
|
||||
-1,
|
||||
)
|
||||
if skip_idx >= 0:
|
||||
messages = messages[skip_idx + 1:]
|
||||
surviving_chars = 0
|
||||
for message in messages:
|
||||
if getattr(message, "hidden", False):
|
||||
continue
|
||||
content = getattr(message, "content", "")
|
||||
if isinstance(content, str):
|
||||
serialized = content
|
||||
else:
|
||||
try:
|
||||
serialized = json.dumps(content, ensure_ascii=False)
|
||||
except Exception:
|
||||
serialized = str(content)
|
||||
surviving_chars += len(serialized)
|
||||
framework_overhead = int(getattr(session, "framework_overhead_tokens", 0) or 0)
|
||||
summary_overhead = 200 if cutoff_msg_id else 0
|
||||
return max(0, framework_overhead + summary_overhead + (surviving_chars // 4))
|
||||
except Exception:
|
||||
logger.debug("post-compact token estimate failed", exc_info=True)
|
||||
return max(0, int(getattr(session, "framework_overhead_tokens", 0) or 0))
|
||||
|
||||
|
||||
def _truncate_large_tool_result(content: object, session_id: str, msg_id: str, max_bytes: int = 50_000) -> tuple[object, str | None]:
|
||||
"""Spill a large tool_result body to disk, return a truncated
|
||||
inline replacement plus the on-disk path (or None if untouched).
|
||||
|
||||
@@ -784,6 +784,66 @@ def test_compact_threshold_default():
|
||||
assert s.context_window == 200_000
|
||||
|
||||
|
||||
def test_post_compact_estimate_excludes_compacted_messages():
|
||||
from backend.apps.agents.core.models import AgentSession, Message
|
||||
from backend.apps.agents.manager.session.history_compaction import (
|
||||
_estimate_post_compact_input,
|
||||
)
|
||||
|
||||
messages = [
|
||||
Message(id=f"m{i}", role="user", content=("old" * 1000 if i < 6 else "keep"))
|
||||
for i in range(8)
|
||||
]
|
||||
s = AgentSession(id="x", name="t", model="sonnet", mode="agent")
|
||||
s.messages = messages
|
||||
s.compacted_through_msg_id = "m5"
|
||||
s.framework_overhead_tokens = 100
|
||||
|
||||
assert _estimate_post_compact_input(s) == 100 + 200 + (len("keepkeep") // 4)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context_update_emitter_refreshes_session_tokens(monkeypatch):
|
||||
import backend.apps.agents.agent_manager as agent_manager_module
|
||||
from backend.apps.agents.agent_manager import AgentManager
|
||||
from backend.apps.agents.core.models import AgentSession
|
||||
|
||||
sent = []
|
||||
|
||||
async def fake_send_to_session(session_id, event, payload):
|
||||
sent.append((session_id, event, payload))
|
||||
|
||||
monkeypatch.setattr(
|
||||
agent_manager_module.ws_manager,
|
||||
"send_to_session",
|
||||
fake_send_to_session,
|
||||
)
|
||||
s = AgentSession(id="x", name="t", model="sonnet", mode="agent")
|
||||
s.context_window = 1_000
|
||||
s.tokens = {"input": 900, "output": 7}
|
||||
s.framework_overhead_tokens = 42
|
||||
s.active_mcps = ["github"]
|
||||
|
||||
await AgentManager()._emit_context_update("x", s, input_tokens=250)
|
||||
|
||||
assert s.tokens == {"input": 250, "output": 7}
|
||||
assert sent == [(
|
||||
"x",
|
||||
"agent:context_update",
|
||||
{
|
||||
"session_id": "x",
|
||||
"input_tokens": 250,
|
||||
"output_tokens": 7,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_read_pct": 0.0,
|
||||
"ctx_used_pct": 0.25,
|
||||
"context_window": 1_000,
|
||||
"framework_overhead_tokens": 42,
|
||||
"active_mcps": ["github"],
|
||||
},
|
||||
)]
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# Group L, Sentence-case display (the parseMcpToolName fix)
|
||||
# ===========================================================================
|
||||
|
||||
Generated
+6
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openswarm",
|
||||
"version": "1.1.70",
|
||||
"version": "1.2.77",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openswarm",
|
||||
"version": "1.1.70",
|
||||
"version": "1.2.77",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"electron-updater": "6.8.3",
|
||||
@@ -567,6 +567,7 @@
|
||||
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -1434,6 +1435,7 @@
|
||||
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.8.1",
|
||||
"builder-util": "26.8.1",
|
||||
@@ -1582,6 +1584,7 @@
|
||||
"integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.8.1",
|
||||
"builder-util": "26.8.1",
|
||||
@@ -2885,6 +2888,7 @@
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
Generated
+18
-1
@@ -84,6 +84,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1964,6 +1965,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -2007,6 +2009,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -2242,6 +2245,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz",
|
||||
"integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@mui/core-downloads-tracker": "^7.3.10",
|
||||
@@ -3374,6 +3378,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
|
||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -3770,6 +3775,7 @@
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3809,6 +3815,7 @@
|
||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -4137,6 +4144,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
@@ -8184,6 +8192,7 @@
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -8240,6 +8249,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -8458,6 +8468,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -8470,6 +8481,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -8516,6 +8528,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -8654,7 +8667,8 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -8966,6 +8980,7 @@
|
||||
"integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.1.5",
|
||||
@@ -10069,6 +10084,7 @@
|
||||
"integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -10117,6 +10133,7 @@
|
||||
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@discoveryjs/json-ext": "^0.5.0",
|
||||
"@webpack-cli/configtest": "^2.1.1",
|
||||
|
||||
@@ -1034,42 +1034,17 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
);
|
||||
})()}
|
||||
{(() => {
|
||||
const liveWindow = session.context_window || contextEstimate.limit || 200_000;
|
||||
const liveInput = session.tokens?.input ?? 0;
|
||||
const pct = liveInput > 0
|
||||
? Math.min(1, liveInput / Math.max(1, liveWindow))
|
||||
: (contextEstimate.used / Math.max(1, liveWindow));
|
||||
const mcpCount = session.active_mcps?.length ?? 0;
|
||||
// Quiet until it matters: hide the memory meter until the chat is filling up,
|
||||
// and hide the tool count unless tools are actually connected.
|
||||
const showMemory = pct >= 0.60;
|
||||
const showTools = mcpCount > 0;
|
||||
if (!showMemory && !showTools) return null;
|
||||
const pctTxt = `${Math.round(pct * 100)}%`;
|
||||
const memColor = pct >= 0.85 ? '#ef4444' : '#f59e0b';
|
||||
const tip = [
|
||||
showMemory ? `Memory ${pctTxt} full. As the chat fills up, the oldest messages start dropping out.` : null,
|
||||
showTools ? `${mcpCount} tool${mcpCount === 1 ? '' : 's'} connected.` : null,
|
||||
].filter(Boolean).join('\n');
|
||||
if (mcpCount === 0) return null;
|
||||
return (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, fontVariantNumeric: 'tabular-nums' }}
|
||||
title={tip}
|
||||
title={`${mcpCount} tool${mcpCount === 1 ? '' : 's'} connected.`}
|
||||
>
|
||||
{showMemory && (
|
||||
<Box component="span" sx={{ color: memColor, fontWeight: 500 }}>
|
||||
Memory {pctTxt} full
|
||||
</Box>
|
||||
)}
|
||||
{showMemory && showTools && (
|
||||
<Box component="span" sx={{ color: c.text.ghost }}>·</Box>
|
||||
)}
|
||||
{showTools && (
|
||||
<Box component="span" sx={{ color: c.text.tertiary }}>
|
||||
{mcpCount} tool{mcpCount === 1 ? '' : 's'}
|
||||
</Box>
|
||||
)}
|
||||
<Box component="span" sx={{ color: c.text.tertiary }}>
|
||||
{mcpCount} tool{mcpCount === 1 ? '' : 's'}
|
||||
</Box>
|
||||
</Typography>
|
||||
);
|
||||
})()}
|
||||
|
||||
@@ -809,9 +809,6 @@ const AgentCard: React.FC<Props> = ({
|
||||
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
|
||||
{friendlyModelLabel}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
|
||||
{session.mode}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
|
||||
<ElapsedTimer messages={session.messages} status={session.status} />
|
||||
</Typography>
|
||||
|
||||
Reference in New Issue
Block a user