Skip to content

Commit 098b729

Browse files
authored
Refine responsive Control UI chat controls
Summary: - Add agent-scoped Control chat session filtering and agent-first session controls. - Refine responsive chat controls, transcript, result-panel, and duplicate-message behavior. - Reduce chat load churn by avoiding duplicate initial avatar refreshes. Verification: - pnpm test ui/src/ui/app-gateway.node.test.ts ui/src/ui/app-gateway-chat-load.node.test.ts ui/src/ui/chat/chat-responsive.browser.test.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/views/chat.test.ts ui/src/ui/app-scroll.test.ts - pnpm test src/plugin-sdk/file-lock.test.ts - pnpm exec oxfmt --check --threads=1 ui/src/ui/chat/chat-responsive.browser.test.ts src/plugin-sdk/file-lock.test.ts - pnpm --dir ui build - Testbox pnpm check:changed: https://github.com/openclaw/openclaw/actions/runs/25309629891 - PR CI on cd22d3d: https://github.com/openclaw/openclaw/actions/runs/25310534399
1 parent 5397667 commit 098b729

26 files changed

Lines changed: 1596 additions & 129 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Docs: https://docs.openclaw.ai
2121
- Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines.
2222
- Agents/verbose: use compact explain-mode tool summaries for `/verbose` and progress drafts by default, with `agents.defaults.toolProgressDetail: "raw"` and per-agent overrides for debugging raw command/detail output.
2323
- Agents/commands: add `/steer <message>` for queue-independent steering of the active current-session run without starting a new turn when the session is idle. (#76934)
24+
- Control UI/chat: add an agent-first filter to the chat session picker, keep chat controls/composer responsive across phone/tablet/desktop widths, keep desktop chat controls on one row, avoid duplicate avatar refreshes during initial chat load, and hide that row while scrolling down the transcript. Thanks @BunsDev.
25+
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so no-op heartbeat acknowledgements stay compact without hiding nearby context.
2426
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
2527
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
2628
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.

docs/web/control-ui.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ Imported themes are stored only in the current browser profile. They are not wri
158158
- During an active send and the final history refresh, the chat view keeps local optimistic user/assistant messages visible if `chat.history` briefly returns an older snapshot; the canonical transcript replaces those local messages once the Gateway history catches up.
159159
- Live `chat` events are delivery state, while `chat.history` is rebuilt from the durable session transcript. After tool-final events the Control UI reloads history and merges only a small optimistic tail; the transcript boundary is documented in [WebChat](/web/webchat).
160160
- `chat.inject` appends an assistant note to the session transcript and broadcasts a `chat` event for UI-only updates (no agent run, no channel delivery).
161+
- The chat header shows the agent filter before the session picker, and the session picker is scoped by the selected agent. Switching agents shows only sessions tied to that agent and falls back to that agent's main session when it has no saved dashboard sessions yet.
162+
- On desktop widths, chat controls stay on one compact row and collapse while scrolling down the transcript; scrolling up, returning to the top, or reaching the bottom restores the controls.
163+
- Consecutive duplicate text-only messages render as one bubble with a count badge. Messages that carry images, attachments, tool output, or canvas previews are left uncollapsed.
161164
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
162165
- Typing `/new` in the Control UI creates and switches to the same fresh dashboard session as New Chat. Typing `/reset` keeps the Gateway's explicit in-place reset for the current session.
163166
- The chat model picker requests the Gateway's configured model view. If `agents.defaults.models` is present, that allowlist drives the picker. Otherwise the picker shows explicit `models.providers.*.models` entries plus providers with usable auth. The full catalog stays available through the debug `models.list` RPC with `view: "all"`.

src/plugin-sdk/file-lock.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ describe("acquireFileLock", () => {
4242
JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }, null, 2),
4343
"utf8",
4444
);
45-
setTimeout(() => {
46-
void fs.rm(lockPath, { force: true });
47-
}, 50);
4845

4946
await expect(acquireFileLock(filePath, options)).rejects.toSatisfy((error) => {
5047
expect(error).toMatchObject({

ui/src/styles/chat/grouped.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,27 @@ img.chat-avatar {
242242
padding-right: 70px;
243243
}
244244

245+
.chat-duplicate-count {
246+
display: inline-flex;
247+
align-items: center;
248+
align-self: flex-start;
249+
min-height: 22px;
250+
margin-top: 8px;
251+
padding: 2px 7px;
252+
border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
253+
border-radius: var(--radius-sm);
254+
background: color-mix(in srgb, var(--panel) 72%, transparent);
255+
color: var(--muted);
256+
font-size: 11px;
257+
font-weight: 700;
258+
line-height: 1;
259+
}
260+
261+
.chat-group.user .chat-duplicate-count {
262+
background: color-mix(in srgb, var(--primary) 12%, transparent);
263+
color: color-mix(in srgb, var(--foreground) 80%, var(--primary) 20%);
264+
}
265+
245266
.chat-bubble-actions {
246267
position: absolute;
247268
top: 6px;

ui/src/styles/chat/layout.css

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
/* Grow, shrink, and use 0 base for proper scrolling */
5656
overflow-y: auto;
5757
overflow-x: hidden;
58-
padding: 0 6px 6px;
58+
padding: 0 clamp(6px, 1vw, 12px) 6px;
5959
margin: 0 0 0 0;
6060
min-height: 0;
6161
/* Allow shrinking for flex scroll behavior */
@@ -602,6 +602,7 @@
602602
display: flex;
603603
align-items: center;
604604
justify-content: space-between;
605+
gap: 8px;
605606
padding: 6px 10px;
606607
border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
607608
}
@@ -1037,20 +1038,50 @@
10371038
}
10381039

10391040
.chat-controls__session {
1040-
min-width: 140px;
1041-
max-width: 300px;
1041+
min-width: 0;
1042+
max-width: none;
1043+
}
1044+
1045+
.chat-controls__agent {
1046+
min-width: 0;
1047+
max-width: none;
10421048
}
10431049

10441050
.chat-controls__session-row {
1045-
display: flex;
1051+
display: grid;
1052+
grid-template-columns:
1053+
minmax(116px, 5fr) minmax(132px, 7fr) minmax(132px, 5fr)
1054+
minmax(128px, 4fr);
1055+
grid-template-areas: "agent session model thinking";
10461056
align-items: center;
1047-
gap: 12px;
1048-
flex-wrap: wrap;
1057+
gap: 8px;
1058+
width: 100%;
1059+
min-width: 0;
1060+
}
1061+
1062+
.chat-controls__session-row--single-agent {
1063+
grid-template-columns: minmax(132px, 7fr) minmax(132px, 5fr) minmax(128px, 4fr);
1064+
grid-template-areas: "session model thinking";
1065+
}
1066+
1067+
.chat-controls__session-picker {
1068+
grid-area: session;
1069+
}
1070+
1071+
.chat-controls__agent {
1072+
grid-area: agent;
10491073
}
10501074

10511075
.chat-controls__model {
1052-
min-width: 170px;
1053-
max-width: 320px;
1076+
grid-area: model;
1077+
min-width: 0;
1078+
max-width: none;
1079+
}
1080+
1081+
.chat-controls__thinking-select {
1082+
grid-area: thinking;
1083+
min-width: 0;
1084+
max-width: none;
10541085
}
10551086

10561087
.chat-controls__thinking {
@@ -1075,13 +1106,25 @@
10751106
.chat-controls__session select {
10761107
padding: 6px 10px;
10771108
font-size: 13px;
1078-
max-width: 300px;
1109+
width: 100%;
1110+
max-width: none;
10791111
overflow: hidden;
10801112
text-overflow: ellipsis;
10811113
}
10821114

1115+
.chat-controls__agent select {
1116+
width: 100%;
1117+
max-width: none;
1118+
}
1119+
10831120
.chat-controls__model select {
1084-
max-width: 320px;
1121+
width: 100%;
1122+
max-width: none;
1123+
}
1124+
1125+
.chat-controls__thinking-select select {
1126+
width: 100%;
1127+
max-width: none;
10851128
}
10861129

10871130
.chat-controls__thinking {
@@ -1105,12 +1148,24 @@
11051148
border-color: rgba(16, 24, 40, 0.15);
11061149
}
11071150

1151+
@media (max-width: 1535px) {
1152+
.chat-controls__thinking-select {
1153+
min-width: 0;
1154+
max-width: none;
1155+
}
1156+
}
1157+
11081158
@media (max-width: 768px) {
11091159
.chat-controls__session {
11101160
min-width: 120px;
11111161
max-width: none;
11121162
}
11131163

1164+
.chat-controls__agent {
1165+
min-width: 120px;
1166+
max-width: none;
1167+
}
1168+
11141169
.chat-controls__model {
11151170
min-width: 140px;
11161171
max-width: none;
@@ -1123,6 +1178,22 @@
11231178
.chat-compose__field textarea {
11241179
min-height: 64px;
11251180
}
1181+
1182+
.agent-chat__input-btn,
1183+
.agent-chat__toolbar .btn--ghost,
1184+
.chat-send-btn {
1185+
width: 44px;
1186+
min-width: 44px;
1187+
height: 44px;
1188+
}
1189+
1190+
.agent-chat__suggestions {
1191+
grid-template-columns: minmax(0, 1fr);
1192+
}
1193+
1194+
.agent-chat__suggestion {
1195+
min-height: 44px;
1196+
}
11261197
}
11271198

11281199
@media (max-width: 640px) {
@@ -1161,6 +1232,11 @@
11611232
.chat-controls__model {
11621233
min-width: 150px;
11631234
}
1235+
1236+
.chat-bubble.has-copy {
1237+
padding-top: 34px;
1238+
padding-right: 12px;
1239+
}
11641240
}
11651241

11661242
/* Chat loading skeleton */
@@ -1177,7 +1253,9 @@
11771253
justify-content: center;
11781254
text-align: center;
11791255
gap: 12px;
1180-
padding: 48px 24px;
1256+
width: min(100%, 560px);
1257+
margin: auto;
1258+
padding: clamp(28px, 6vh, 56px) 24px;
11811259
flex: 1;
11821260
min-height: 0;
11831261
}
@@ -1266,18 +1344,18 @@
12661344
}
12671345

12681346
.agent-chat__suggestions {
1269-
display: flex;
1270-
flex-wrap: wrap;
1347+
display: grid;
1348+
grid-template-columns: repeat(2, minmax(0, 1fr));
12711349
gap: 8px;
1272-
justify-content: center;
1273-
max-width: 480px;
1350+
width: min(100%, 480px);
12741351
margin-top: 8px;
12751352
}
12761353

12771354
.agent-chat__suggestion {
1355+
min-height: 40px;
12781356
font-size: 13px;
12791357
padding: 8px 16px;
1280-
border-radius: var(--radius-full);
1358+
border-radius: var(--radius-md);
12811359
border: 1px solid var(--border);
12821360
background: var(--panel);
12831361
color: var(--foreground);

ui/src/styles/components.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,32 @@
18211821
margin-bottom: 0;
18221822
}
18231823

1824+
@media (max-width: 768px) {
1825+
.chat-side-result {
1826+
position: fixed;
1827+
left: max(8px, var(--safe-area-left));
1828+
right: max(8px, var(--safe-area-right));
1829+
bottom: calc(108px + var(--safe-area-bottom));
1830+
z-index: 35;
1831+
width: auto;
1832+
max-height: min(42vh, 320px);
1833+
margin: 0;
1834+
overflow: auto;
1835+
background: color-mix(in srgb, var(--card) 96%, var(--info) 4%);
1836+
box-shadow: var(--shadow-lg);
1837+
}
1838+
1839+
.chat-side-result--error {
1840+
background: color-mix(in srgb, var(--card) 96%, var(--danger) 4%);
1841+
}
1842+
1843+
.chat-side-result__dismiss {
1844+
width: 44px;
1845+
min-width: 44px;
1846+
height: 44px;
1847+
}
1848+
}
1849+
18241850
/* ===========================================
18251851
Code Blocks
18261852
=========================================== */

ui/src/styles/layout.css

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,16 @@
980980
pointer-events: none;
981981
}
982982

983+
.content--chat .content-header.content-header--chat-hidden {
984+
opacity: 0;
985+
transform: translateY(-10px);
986+
max-height: 0px;
987+
padding-top: 0;
988+
padding-bottom: 0;
989+
overflow: hidden;
990+
pointer-events: none;
991+
}
992+
983993
.page-title {
984994
font-size: 22px;
985995
font-weight: 650;
@@ -1094,25 +1104,30 @@
10941104

10951105
/* Chat view header adjustments */
10961106
.content--chat .content-header {
1097-
flex-direction: row;
1107+
display: grid;
1108+
grid-template-columns: minmax(0, 1fr) max-content;
10981109
align-items: center;
10991110
justify-content: space-between;
1100-
gap: 16px;
1111+
gap: 12px;
11011112
padding-bottom: 0;
11021113
overflow: visible;
1114+
max-height: 56px;
11031115
}
11041116

11051117
.content--chat .content-header > div:first-child {
11061118
text-align: left;
1119+
min-width: 0;
11071120
}
11081121

11091122
.content--chat .page-meta {
1110-
justify-content: flex-start;
1123+
justify-content: flex-end;
1124+
min-width: max-content;
11111125
overflow: visible;
11121126
}
11131127

11141128
.content--chat .chat-controls {
11151129
flex-shrink: 0;
1130+
flex-wrap: nowrap;
11161131
}
11171132

11181133
/* ===========================================

0 commit comments

Comments
 (0)