mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
feat(chat-ui): redesign composer bottom bar with split Talk control and reasoning-effort chip (#100445)
* feat(chat-ui): redesign composer bottom bar with split Talk control and reasoning-effort chip Removes the toolbar divider so the composer frame is the only chrome, merges the Talk toggle and Talk settings gear into a mic+caret split control, shows the effective reasoning level as a subtle pill next to the model name (accent-washed only on session override), right-aligns the model/settings cluster next to Send, and softens active/selected highlight states across the bar. The combined model menu and reasoning fly-out now anchor right so they stay on screen. * fix(chat-ui): keep mobile composer controls usable * fix(chat-ui): keep reasoning controls in mobile viewport * fix(chat-ui): simplify composer placeholder * chore(i18n): refresh composer locale metadata
This commit is contained in:
committed by
GitHub
parent
c4a862ab53
commit
927bbeb58c
@@ -240,9 +240,9 @@ The terminal is also available as a full-screen, terminal-only document at `/?vi
|
||||
<Accordion title="Talk mode (browser realtime)">
|
||||
Talk mode uses a registered realtime voice provider. Configure OpenAI with `talk.realtime.provider: "openai"` plus an `openai` API-key auth profile, `talk.realtime.providers.openai.apiKey`, or `OPENAI_API_KEY`; OpenAI OAuth profiles do not configure Realtime voice. Configure Google with `talk.realtime.provider: "google"` plus `talk.realtime.providers.google.apiKey`. The browser never receives a standard provider API key: OpenAI receives an ephemeral Realtime client secret for WebRTC, and Google Live receives a one-use constrained Live API auth token for a browser WebSocket session, with instructions and tool declarations locked into the token by the Gateway. Providers that only expose a backend realtime bridge run through the Gateway relay transport, so credentials and vendor sockets stay server-side while browser audio moves through authenticated Gateway RPCs. The Realtime session prompt is assembled by the Gateway; `talk.client.create` does not accept caller-provided instruction overrides.
|
||||
|
||||
The Chat composer includes a Talk options button next to the Talk start/stop button. Its compact panel keeps only Voice, Model, and Sensitivity for the next Talk session. **More in Settings** opens **Settings → Communications → Talk**, where persistent provider, transport, reasoning effort, exact VAD threshold, silence duration, and prefix padding defaults live; changing those defaults requires `operator.admin` access. Blank composer values fall back to those configured defaults or the provider default. Configuring Gateway relay forces the backend relay path; configuring WebRTC keeps the session client-owned and fails instead of silently falling back to relay if the provider cannot create a browser session.
|
||||
The Chat composer includes a Talk options caret next to the Talk start/stop button. Its compact panel keeps only Voice, Model, and Sensitivity for the next Talk session. **More in Settings** opens **Settings → Communications → Talk**, where persistent provider, transport, reasoning effort, exact VAD threshold, silence duration, and prefix padding defaults live; changing those defaults requires `operator.admin` access. Blank composer values fall back to those configured defaults or the provider default. Configuring Gateway relay forces the backend relay path; configuring WebRTC keeps the session client-owned and fails instead of silently falling back to relay if the provider cannot create a browser session.
|
||||
|
||||
The Talk control itself is the waves button next to the microphone dictation button. When Talk starts, the composer status row shows `Connecting Talk...`, then `Talk live` while audio is connected, or `Asking OpenClaw...` while a realtime tool call is consulting the configured larger model through `talk.client.toolCall`.
|
||||
The Talk control itself is the microphone button in the composer toolbar, with a small caret beside it that opens Talk options. When Talk starts, the composer status row shows `Connecting Talk...`, then `Talk live` while audio is connected, or `Asking OpenClaw...` while a realtime tool call is consulting the configured larger model through `talk.client.toolCall`.
|
||||
|
||||
Maintainer live smoke: `OPENAI_API_KEY=... GEMINI_API_KEY=... node --import tsx scripts/dev/realtime-talk-live-smoke.ts` verifies the OpenAI backend WebSocket bridge, OpenAI browser WebRTC SDP exchange, Google Live constrained-token browser WebSocket setup, and the Gateway relay browser adapter with fake microphone media. The command prints provider status only and does not log secrets.
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:01:23.261Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.320Z",
|
||||
"locale": "ar",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T20:59:33.063Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.310Z",
|
||||
"locale": "de",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:00:39.848Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.454Z",
|
||||
"locale": "es",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:03:19.039Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.693Z",
|
||||
"locale": "fa",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:00:52.467Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.964Z",
|
||||
"locale": "fr",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T19:32:32.620Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.150Z",
|
||||
"locale": "hi",
|
||||
"model": "",
|
||||
"provider": "",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:02:12.906Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.968Z",
|
||||
"locale": "id",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:01:24.757Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.470Z",
|
||||
"locale": "it",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:00:35.490Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.625Z",
|
||||
"locale": "ja-JP",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:00:39.218Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.784Z",
|
||||
"locale": "ko",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:03:15.146Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.554Z",
|
||||
"locale": "nl",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:02:29.694Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.112Z",
|
||||
"locale": "pl",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T20:59:44.069Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.155Z",
|
||||
"locale": "pt-BR",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T19:32:34.753Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.849Z",
|
||||
"locale": "ru",
|
||||
"model": "",
|
||||
"provider": "",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:02:41.557Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.261Z",
|
||||
"locale": "th",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:01:42.084Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.624Z",
|
||||
"locale": "tr",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:01:40.702Z",
|
||||
"generatedAt": "2026-07-05T22:25:08.790Z",
|
||||
"locale": "uk",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T21:03:06.663Z",
|
||||
"generatedAt": "2026-07-05T22:25:09.425Z",
|
||||
"locale": "vi",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T20:59:46.734Z",
|
||||
"generatedAt": "2026-07-05T22:25:06.843Z",
|
||||
"locale": "zh-CN",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-07-05T20:59:36.968Z",
|
||||
"generatedAt": "2026-07-05T22:25:07.009Z",
|
||||
"locale": "zh-TW",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "116e85303569dc538dcd374ee7788ea9664fd8aae5cda4a2f0f2451cfefeaccf",
|
||||
"sourceHash": "53b87190ae24c60a0c03150797350e7757cb8c3aad65967cf6b64f851774fead",
|
||||
"totalKeys": 1528,
|
||||
"translatedKeys": 1528,
|
||||
"workflow": 1
|
||||
|
||||
Generated
+1
-1
@@ -1498,7 +1498,7 @@ export const ar: TranslationMap = {
|
||||
badge: "منتهي الصلاحية",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1525,7 +1525,7 @@ export const de: TranslationMap = {
|
||||
badge: "Abgelaufen",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
@@ -1504,7 +1504,7 @@ export const en: TranslationMap = {
|
||||
badge: "Expired",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1523,7 +1523,7 @@ export const es: TranslationMap = {
|
||||
badge: "Caducado",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1516,7 +1516,7 @@ export const fa: TranslationMap = {
|
||||
badge: "منقضیشده",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1530,7 +1530,7 @@ export const fr: TranslationMap = {
|
||||
badge: "Expiré",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1500,7 +1500,7 @@ export const hi: TranslationMap = {
|
||||
badge: "समाप्त",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "{name} को संदेश भेजें (भेजने के लिए Enter)",
|
||||
placeholder: "{name} को संदेश भेजें",
|
||||
placeholderWithAttachments: "संदेश जोड़ें या और छवियाँ पेस्ट करें...",
|
||||
placeholderDisconnected: "चैट शुरू करने के लिए gateway से कनेक्ट करें...",
|
||||
attachFile: "फ़ाइल संलग्न करें",
|
||||
|
||||
Generated
+1
-1
@@ -1514,7 +1514,7 @@ export const id: TranslationMap = {
|
||||
badge: "Kedaluwarsa",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1523,7 +1523,7 @@ export const it: TranslationMap = {
|
||||
badge: "Scaduto",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1520,7 +1520,7 @@ export const ja_JP: TranslationMap = {
|
||||
badge: "期限切れ",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1505,7 +1505,7 @@ export const ko: TranslationMap = {
|
||||
badge: "만료됨",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1520,7 +1520,7 @@ export const nl: TranslationMap = {
|
||||
badge: "Verlopen",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1519,7 +1519,7 @@ export const pl: TranslationMap = {
|
||||
badge: "Wygasł",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1516,7 +1516,7 @@ export const pt_BR: TranslationMap = {
|
||||
badge: "Expirado",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1525,7 +1525,7 @@ export const ru: TranslationMap = {
|
||||
badge: "Истек",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Сообщение {name} (Enter для отправки)",
|
||||
placeholder: "Сообщение {name}",
|
||||
placeholderWithAttachments: "Добавьте сообщение или вставьте еще изображения...",
|
||||
placeholderDisconnected: "Подключитесь к шлюзу, чтобы начать общение...",
|
||||
attachFile: "Прикрепить файл",
|
||||
|
||||
Generated
+1
-1
@@ -1482,7 +1482,7 @@ export const th: TranslationMap = {
|
||||
badge: "หมดอายุ",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1520,7 +1520,7 @@ export const tr: TranslationMap = {
|
||||
badge: "Süresi doldu",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1518,7 +1518,7 @@ export const uk: TranslationMap = {
|
||||
badge: "Застарів",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1506,7 +1506,7 @@ export const vi: TranslationMap = {
|
||||
badge: "Đã hết hạn",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
Generated
+1
-1
@@ -1477,7 +1477,7 @@ export const zh_CN: TranslationMap = {
|
||||
badge: "已过期",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "给 {name} 发消息(Enter 发送)",
|
||||
placeholder: "给 {name} 发消息",
|
||||
placeholderWithAttachments: "添加消息或继续粘贴图片...",
|
||||
placeholderDisconnected: "连接到 Gateway 后开始聊天...",
|
||||
attachFile: "附加文件",
|
||||
|
||||
Generated
+1
-1
@@ -1479,7 +1479,7 @@ export const zh_TW: TranslationMap = {
|
||||
badge: "已過期",
|
||||
},
|
||||
composer: {
|
||||
placeholder: "Message {name} (Enter to send)",
|
||||
placeholder: "Message {name}",
|
||||
placeholderWithAttachments: "Add a message or paste more images...",
|
||||
placeholderDisconnected: "Connect to the gateway to start chatting...",
|
||||
attachFile: "Attach file",
|
||||
|
||||
@@ -177,7 +177,8 @@ function composerControlsHtml() {
|
||||
<div class="chat-composer-model-control">
|
||||
<details class="chat-controls__session chat-controls__inline-select chat-controls__model">
|
||||
<summary class="chat-controls__inline-select-trigger" data-chat-composer-model="true" aria-label="Chat model">
|
||||
<span class="chat-controls__inline-select-label">Default model · Off</span>
|
||||
<span class="chat-controls__inline-select-label">Default model</span>
|
||||
<span class="chat-controls__effort-chip">Off</span>
|
||||
<span class="chat-controls__inline-select-icon">${iconSvg()}</span>
|
||||
</summary>
|
||||
<div class="chat-controls__inline-select-menu chat-controls__inline-select-menu--combined">
|
||||
@@ -186,6 +187,7 @@ function composerControlsHtml() {
|
||||
<button class="chat-controls__inline-select-option chat-controls__combined-model-option">gpt-5.5</button>
|
||||
<button class="chat-controls__inline-select-option chat-controls__combined-model-option">claude-sonnet-4-6</button>
|
||||
</div>
|
||||
<div class="chat-controls__reasoning-panel">Reasoning</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@@ -334,8 +336,10 @@ function chatHtml(
|
||||
<div class="agent-chat__toolbar">
|
||||
<div class="agent-chat__toolbar-left">
|
||||
<button class="agent-chat__input-btn">${iconSvg()}</button>
|
||||
<button class="agent-chat__input-btn">${iconSvg()}</button>
|
||||
<button class="agent-chat__input-btn">${iconSvg()}</button>
|
||||
<div class="agent-chat__talk-group">
|
||||
<button class="agent-chat__input-btn agent-chat__talk-toggle">${iconSvg()}</button>
|
||||
<button class="agent-chat__input-btn agent-chat__talk-caret">${iconSvg()}</button>
|
||||
</div>
|
||||
<span class="agent-chat__token-count">8</span>
|
||||
</div>
|
||||
${composerControlsHtml()}
|
||||
@@ -804,6 +808,29 @@ describeBrowserLayout("chat responsive browser layout", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
[320, 568],
|
||||
[375, 812],
|
||||
[667, 375],
|
||||
[768, 500],
|
||||
] as const)(
|
||||
"keeps the composer model menu inside the mobile viewport at %sx%s",
|
||||
async (width, height) => {
|
||||
const page = await openFixture(width, height);
|
||||
try {
|
||||
await page.locator('[data-chat-composer-model="true"]').click();
|
||||
const menu = await getBoundingBox(page, ".chat-controls__inline-select-menu--combined");
|
||||
const reasoning = await getBoundingBox(page, ".chat-controls__reasoning-panel");
|
||||
expect(menu.x).toBeGreaterThanOrEqual(0);
|
||||
expect(menu.x + menu.width).toBeLessThanOrEqual(width + 1);
|
||||
expect(reasoning.x).toBeGreaterThanOrEqual(0);
|
||||
expect(reasoning.x + reasoning.width).toBeLessThanOrEqual(width + 1);
|
||||
} finally {
|
||||
await closeBrowserPage(page);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[320, 568],
|
||||
[393, 852],
|
||||
|
||||
@@ -2000,49 +2000,55 @@ export function renderChatComposer(props: ChatComposerProps) {
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
|
||||
${props.onToggleRealtimeTalk
|
||||
${props.onToggleRealtimeTalk || props.onToggleRealtimeTalkOptions
|
||||
? html`
|
||||
<openclaw-tooltip
|
||||
.content=${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}
|
||||
>
|
||||
<button
|
||||
class="agent-chat__input-btn ${props.realtimeTalkActive
|
||||
? "agent-chat__input-btn--talk"
|
||||
: ""}"
|
||||
@click=${props.onToggleRealtimeTalk}
|
||||
aria-label=${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}
|
||||
?disabled=${!canCompose && !props.realtimeTalkActive}
|
||||
>
|
||||
${props.realtimeTalkActive ? icons.volume2 : icons.radio}
|
||||
<span class="agent-chat__control-label"
|
||||
>${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}</span
|
||||
>
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing}
|
||||
${props.onToggleRealtimeTalkOptions
|
||||
? html`
|
||||
<openclaw-tooltip content="Talk settings">
|
||||
<button
|
||||
class="agent-chat__input-btn ${props.realtimeTalkOptionsOpen
|
||||
? "agent-chat__input-btn--talk"
|
||||
: ""}"
|
||||
@click=${props.onToggleRealtimeTalkOptions}
|
||||
aria-label="Talk settings"
|
||||
aria-expanded=${props.realtimeTalkOptionsOpen ? "true" : "false"}
|
||||
?disabled=${!canCompose || props.realtimeTalkActive}
|
||||
>
|
||||
${icons.settings}
|
||||
<span class="agent-chat__control-label">Talk settings</span>
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
<div class="agent-chat__talk-group">
|
||||
${props.onToggleRealtimeTalk
|
||||
? html`
|
||||
<openclaw-tooltip
|
||||
.content=${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}
|
||||
>
|
||||
<button
|
||||
class="agent-chat__input-btn agent-chat__talk-toggle ${props.realtimeTalkActive
|
||||
? "agent-chat__input-btn--talk"
|
||||
: ""}"
|
||||
@click=${props.onToggleRealtimeTalk}
|
||||
aria-label=${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}
|
||||
?disabled=${!canCompose && !props.realtimeTalkActive}
|
||||
>
|
||||
${props.realtimeTalkActive ? icons.volume2 : icons.mic}
|
||||
<span class="agent-chat__control-label"
|
||||
>${props.realtimeTalkActive
|
||||
? t("chat.composer.stopTalk")
|
||||
: t("chat.composer.startTalk")}</span
|
||||
>
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing}
|
||||
${props.onToggleRealtimeTalkOptions
|
||||
? html`
|
||||
<openclaw-tooltip content="Talk settings">
|
||||
<button
|
||||
class="agent-chat__input-btn agent-chat__talk-caret ${props.realtimeTalkOptionsOpen
|
||||
? "agent-chat__input-btn--open"
|
||||
: ""}"
|
||||
@click=${props.onToggleRealtimeTalkOptions}
|
||||
aria-label="Talk settings"
|
||||
aria-expanded=${props.realtimeTalkOptionsOpen ? "true" : "false"}
|
||||
?disabled=${!canCompose || props.realtimeTalkActive}
|
||||
>
|
||||
${icons.chevronDown}
|
||||
<span class="agent-chat__control-label">Talk settings</span>
|
||||
</button>
|
||||
</openclaw-tooltip>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${tokens ? html`<span class="agent-chat__token-count">${tokens}</span>` : nothing}
|
||||
|
||||
@@ -153,8 +153,6 @@ function renderChatModelReasoningSelect(params: {
|
||||
const triggerModel = formatCombinedPickerModelLabel(selectedModelLabel);
|
||||
const triggerThinking = formatCombinedPickerThinkingLabel(selectedThinkingLabel);
|
||||
const triggerTitle = `${triggerModel} · ${triggerThinking}`;
|
||||
const triggerLabel =
|
||||
selectedThinkingValue === "" ? triggerModel : `${triggerModel} · ${triggerThinking}`;
|
||||
const sliderStops = thinkingOptions.filter((option) => option.value !== "");
|
||||
const defaultStopIndex = sliderStops.findIndex((option) => option.value === thinkingDefaultValue);
|
||||
const hasThinkingOverride = selectedThinkingValue !== "";
|
||||
@@ -206,7 +204,16 @@ function renderChatModelReasoningSelect(params: {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="chat-controls__inline-select-label">${triggerLabel}</span>
|
||||
<span class="chat-controls__inline-select-label">${triggerModel}</span>
|
||||
${showReasoning || hasThinkingOverride
|
||||
? html`<span
|
||||
class="chat-controls__effort-chip ${hasThinkingOverride
|
||||
? "chat-controls__effort-chip--override"
|
||||
: ""}"
|
||||
aria-hidden="true"
|
||||
>${triggerThinking}</span
|
||||
>`
|
||||
: ""}
|
||||
<span class="chat-controls__inline-select-icon" aria-hidden="true">
|
||||
${icons.chevronDown}
|
||||
</span>
|
||||
|
||||
+102
-22
@@ -819,13 +819,15 @@ openclaw-chat-page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Model/effort/settings cluster hugs the send button on the right; the left
|
||||
toolbar half absorbs all spare width. */
|
||||
.agent-chat__composer-controls {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
flex: 1 1 auto;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -902,7 +904,7 @@ openclaw-chat-page {
|
||||
|
||||
.chat-settings-chip:hover,
|
||||
.chat-settings-chip--open {
|
||||
background: var(--bg-hover);
|
||||
background: color-mix(in srgb, var(--text) 7%, transparent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@@ -1013,13 +1015,14 @@ openclaw-chat-page {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Chromeless status row: the composer frame is the only chrome, so the
|
||||
toolbar sits flush inside it with no divider line. */
|
||||
.agent-chat__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
|
||||
padding: 2px 8px 8px;
|
||||
}
|
||||
|
||||
.agent-chat__toolbar-left,
|
||||
@@ -1079,14 +1082,44 @@ openclaw-chat-page {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.agent-chat__input-btn--active {
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
/* Selected/open states stay a quiet neutral wash; only live recording gets a
|
||||
(soft) danger tint so "active" chrome never competes with the send button. */
|
||||
.agent-chat__input-btn--active,
|
||||
.agent-chat__input-btn--open {
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--text) 7%, transparent);
|
||||
}
|
||||
|
||||
.agent-chat__input-btn--talk {
|
||||
color: var(--danger, #ef4444);
|
||||
background: color-mix(in srgb, var(--danger, #ef4444) 14%, transparent);
|
||||
background: color-mix(in srgb, var(--danger, #ef4444) 9%, transparent);
|
||||
}
|
||||
|
||||
/* Split talk control: mic toggles Talk, the narrow caret opens Talk settings
|
||||
(mirrors a mic-with-dropdown control; negative margin keeps the pair reading
|
||||
as one unit without a wrapper border). */
|
||||
.agent-chat__talk-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-chat__talk-group .agent-chat__talk-toggle {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.agent-chat__talk-group .agent-chat__talk-caret {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
margin-left: -2px;
|
||||
padding: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.agent-chat__talk-group .agent-chat__talk-caret svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.agent-chat__talk-status {
|
||||
@@ -1414,9 +1447,11 @@ openclaw-chat-page {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Status chips keep their semantic hue in the text/icon only; borders stay
|
||||
near-neutral so the row reads as quiet metadata, not alerts. */
|
||||
.agent-chat__run-status--in-progress {
|
||||
color: var(--info);
|
||||
border-color: color-mix(in srgb, var(--info) 32%, transparent);
|
||||
border-color: color-mix(in srgb, var(--info) 18%, transparent);
|
||||
}
|
||||
|
||||
.agent-chat__run-status--in-progress svg {
|
||||
@@ -1425,12 +1460,12 @@ openclaw-chat-page {
|
||||
|
||||
.agent-chat__run-status--done {
|
||||
color: var(--ok);
|
||||
border-color: color-mix(in srgb, var(--ok) 32%, transparent);
|
||||
border-color: color-mix(in srgb, var(--ok) 18%, transparent);
|
||||
}
|
||||
|
||||
.agent-chat__run-status--interrupted {
|
||||
color: var(--warn);
|
||||
border-color: color-mix(in srgb, var(--warn) 36%, transparent);
|
||||
border-color: color-mix(in srgb, var(--warn) 22%, transparent);
|
||||
}
|
||||
|
||||
@keyframes chat-run-status-spin {
|
||||
@@ -2260,7 +2295,7 @@ openclaw-chat-page {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
@@ -2301,10 +2336,37 @@ openclaw-chat-page {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Effort chip inside the model trigger: shows the effective thinking level as
|
||||
a muted pill (accent-washed when the session overrides the default). It is
|
||||
decorative (aria-hidden); the trigger aria-label carries the full state. */
|
||||
.chat-controls__effort-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
height: 18px;
|
||||
padding: 0 7px;
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, var(--text) 6%, transparent);
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-controls__effort-chip--override {
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
color: color-mix(in srgb, var(--accent) 72%, var(--text));
|
||||
}
|
||||
|
||||
/* Chevron pins right so label+chip stay grouped when the trigger stretches
|
||||
(mobile grid cell); on desktop the trigger is content-sized so this is a
|
||||
no-op vs the old space-between layout. */
|
||||
.chat-controls__inline-select-icon {
|
||||
display: inline-flex;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: auto;
|
||||
color: var(--muted);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@@ -2368,7 +2430,12 @@ openclaw-chat-page {
|
||||
background: color-mix(in srgb, var(--text) 6%, transparent);
|
||||
}
|
||||
|
||||
/* The combined model picker anchors to a right-aligned trigger, so the menu
|
||||
grows leftward and the reasoning panel flies out to the left; anchoring
|
||||
left like the generic menu would clip both at the viewport edge. */
|
||||
.chat-controls__inline-select-menu--combined {
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: max(100%, min(320px, calc(100vw - 32px)));
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
@@ -2403,7 +2470,7 @@ openclaw-chat-page {
|
||||
.chat-controls__reasoning-panel {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: calc(100% + 8px);
|
||||
right: calc(100% + 8px);
|
||||
z-index: 90;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -2418,7 +2485,7 @@ openclaw-chat-page {
|
||||
overflow-y: auto;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translateX(-4px);
|
||||
transform: translateX(4px);
|
||||
transition:
|
||||
opacity var(--duration-fast) ease,
|
||||
transform var(--duration-fast) ease;
|
||||
@@ -2800,6 +2867,26 @@ openclaw-chat-page {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
/* Preserve the phone touch target despite the narrower desktop split caret. */
|
||||
.agent-chat__talk-group .agent-chat__talk-caret {
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
/* The model trigger owns the left mobile grid column; grow its menu rightward. */
|
||||
.chat-controls__inline-select-menu--combined {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.chat-controls__reasoning-panel {
|
||||
top: auto;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: min(220px, calc(100vw - 48px));
|
||||
}
|
||||
|
||||
.agent-chat__input {
|
||||
margin: 0 8px calc(14px + var(--safe-area-bottom));
|
||||
}
|
||||
@@ -2849,13 +2936,6 @@ openclaw-chat-page {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.chat-controls__reasoning-panel {
|
||||
top: auto;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 0;
|
||||
width: min(220px, calc(100vw - 48px));
|
||||
}
|
||||
|
||||
.chat-controls {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user