@linxin666/dsh-remote-web-ui 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +5 -2
- package/README.zh.md +2 -2
- package/lib/client.js +48 -58
- package/lib/client.js.map +1 -1
- package/lib/mobile.js +649 -45
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +11 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +11 -5
- package/lib/types/mobile/markdown.d.ts +26 -0
- package/lib/types/mobile/markdown.d.ts.map +1 -0
- package/lib/types/mobile/markdown.js +272 -0
- package/lib/types/mobile/markdown.test.d.ts +2 -0
- package/lib/types/mobile/markdown.test.d.ts.map +1 -0
- package/lib/types/mobile/markdown.test.js +67 -0
- package/lib/types/mobile/messages.d.ts +14 -0
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +54 -2
- package/lib/types/mobile/messages.test.js +99 -0
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +167 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +81 -5
- package/lib/types/mobile/views/ChatView.test.js +105 -0
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +50 -47
- package/src/client/settings-card.module.css +9 -0
- package/src/mobile/markdown.test.ts +77 -0
- package/src/mobile/markdown.ts +278 -0
- package/src/mobile/messages.test.ts +106 -0
- package/src/mobile/messages.ts +66 -2
- package/src/mobile/mobile-styles.ts +167 -0
- package/src/mobile/views/ChatView.test.tsx +121 -0
- package/src/mobile/views/ChatView.tsx +164 -6
|
@@ -186,4 +186,103 @@ describe('foldEvents', () => {
|
|
|
186
186
|
foldEvents([makeEvent('assistant/message', assistantMessageData('a-1', 0, 0, 'world'), 1)], first);
|
|
187
187
|
expect(JSON.stringify(first)).toBe(snapshot);
|
|
188
188
|
});
|
|
189
|
+
it('request/context sets the context window used by a later assistant/message with usage', () => {
|
|
190
|
+
const events = [
|
|
191
|
+
makeEvent('request/context', { provider: 'fx', model: 'fx-1', contextWindow: 100_000 }, 0),
|
|
192
|
+
makeEvent('user/message', userMessageData('u-1', 'hi'), 1),
|
|
193
|
+
makeEvent('assistant/message', {
|
|
194
|
+
turn: 0,
|
|
195
|
+
step: 0,
|
|
196
|
+
message: { id: 'a-1', role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
|
|
197
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
198
|
+
}, 2),
|
|
199
|
+
];
|
|
200
|
+
const result = foldEvents(events);
|
|
201
|
+
const assistant = result.find(message => message.kind === 'assistant');
|
|
202
|
+
expect(assistant).toMatchObject({
|
|
203
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
204
|
+
contextWindow: 100_000,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
it('usage without a context window carries no contextWindow', () => {
|
|
208
|
+
const events = [
|
|
209
|
+
makeEvent('user/message', userMessageData('u-1', 'hi'), 0),
|
|
210
|
+
makeEvent('assistant/message', {
|
|
211
|
+
turn: 0,
|
|
212
|
+
step: 0,
|
|
213
|
+
message: { id: 'a-1', role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
|
|
214
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
215
|
+
}, 1),
|
|
216
|
+
];
|
|
217
|
+
const result = foldEvents(events);
|
|
218
|
+
const assistant = result.find(message => message.kind === 'assistant');
|
|
219
|
+
expect(assistant).toMatchObject({ usage: { inputTokens: 10, outputTokens: 5 } });
|
|
220
|
+
expect(assistant?.contextWindow).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
it('usage and contextWindow are both attached when request/context precedes the message', () => {
|
|
223
|
+
const events = [
|
|
224
|
+
makeEvent('request/context', { provider: 'fx', model: 'fx-1', contextWindow: 200_000 }, 0),
|
|
225
|
+
makeEvent('user/message', userMessageData('u-1', 'hi'), 1),
|
|
226
|
+
makeEvent('assistant/message', {
|
|
227
|
+
turn: 0,
|
|
228
|
+
step: 0,
|
|
229
|
+
message: { id: 'a-1', role: 'assistant', content: [{ type: 'text', text: 'ok' }] },
|
|
230
|
+
usage: { inputTokens: 30, outputTokens: 2, cacheReadTokens: 8, cacheWriteTokens: 1 },
|
|
231
|
+
}, 2),
|
|
232
|
+
];
|
|
233
|
+
const result = foldEvents(events);
|
|
234
|
+
const assistant = result.find(message => message.kind === 'assistant');
|
|
235
|
+
expect(assistant).toMatchObject({
|
|
236
|
+
usage: { inputTokens: 30, outputTokens: 2, cacheReadTokens: 8, cacheWriteTokens: 1 },
|
|
237
|
+
contextWindow: 200_000,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
it('assistant/message without usage has no usage field', () => {
|
|
241
|
+
const events = [
|
|
242
|
+
makeEvent('user/message', userMessageData('u-1', 'hi'), 0),
|
|
243
|
+
makeEvent('assistant/message', { turn: 0, step: 0, message: { id: 'a-1', role: 'assistant', content: [{ type: 'text', text: 'ok' }] } }, 1),
|
|
244
|
+
];
|
|
245
|
+
const result = foldEvents(events);
|
|
246
|
+
const assistant = result.find(message => message.kind === 'assistant');
|
|
247
|
+
expect(assistant?.usage).toBeUndefined();
|
|
248
|
+
expect(assistant?.contextWindow).toBeUndefined();
|
|
249
|
+
});
|
|
250
|
+
it('attaches sourceKind from source.kind for a user message', () => {
|
|
251
|
+
const events = [
|
|
252
|
+
makeEvent('user/message', {
|
|
253
|
+
id: 'u-plugin',
|
|
254
|
+
role: 'user',
|
|
255
|
+
content: [{ type: 'text', text: '系统注入' }],
|
|
256
|
+
source: { kind: 'plugin', name: 'react-extension' },
|
|
257
|
+
}, 0),
|
|
258
|
+
];
|
|
259
|
+
const result = foldEvents(events);
|
|
260
|
+
expect(result[0]).toMatchObject({ id: 'u-plugin', kind: 'user', sourceKind: 'plugin' });
|
|
261
|
+
});
|
|
262
|
+
it('keeps sourceKind on a replayed/replaced user/message', () => {
|
|
263
|
+
const first = foldEvents([makeEvent('user/message', {
|
|
264
|
+
id: 'u-1',
|
|
265
|
+
role: 'user',
|
|
266
|
+
content: [{ type: 'text', text: '第一版' }],
|
|
267
|
+
source: { kind: 'plugin' },
|
|
268
|
+
}, 0)]);
|
|
269
|
+
const second = foldEvents([makeEvent('user/message', {
|
|
270
|
+
id: 'u-1',
|
|
271
|
+
role: 'user',
|
|
272
|
+
content: [{ type: 'text', text: '第二版' }],
|
|
273
|
+
source: { kind: 'plugin' },
|
|
274
|
+
}, 1)], first);
|
|
275
|
+
expect(second[0]).toMatchObject({ id: 'u-1', text: '第二版', sourceKind: 'plugin' });
|
|
276
|
+
});
|
|
277
|
+
it('request/context and unknown events are still ignored by the fold', () => {
|
|
278
|
+
const events = [
|
|
279
|
+
makeEvent('some/future-event', { nope: true }, 0),
|
|
280
|
+
makeEvent('request/context', { provider: 'fx', model: 'fx-1' }, 1),
|
|
281
|
+
makeEvent('user/message', userMessageData('u-1', '真实消息'), 2),
|
|
282
|
+
];
|
|
283
|
+
const result = foldEvents(events);
|
|
284
|
+
// request/context with no window and the unknown event render nothing.
|
|
285
|
+
expect(result).toHaveLength(1);
|
|
286
|
+
expect(result[0]).toMatchObject({ id: 'u-1', kind: 'user' });
|
|
287
|
+
});
|
|
189
288
|
});
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Mobile surface stylesheet, shipped as a string and injected at boot (the
|
|
3
3
|
* standalone page has no CSS-module pipeline).
|
|
4
4
|
*/
|
|
5
|
-
export declare const mobileCss = "/* Mobile surface chrome: standalone stylesheet (no main-UI design tokens \u2014\n this page boots without the shell). Light-first palette; the dark palette\n applies only when the header toggle sets [data-theme='dark']. */\n\n:root {\n --m-bg: #f3f5f9;\n --m-bg-raised: #ffffff;\n --m-bg-input: #ffffff;\n --m-border: #dfe4ec;\n --m-text: #1c2333;\n --m-text-secondary: #4d5768;\n --m-text-tertiary: #8b95a7;\n --m-accent: #2f6fed;\n --m-accent-soft: rgba(47, 111, 237, 0.12);\n --m-danger: #d64545;\n --m-success: #1e9e6a;\n --m-radius: 12px;\n --m-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);\n --m-shadow-raise: 0 6px 20px rgba(16, 24, 40, 0.1);\n --m-backdrop: rgba(15, 23, 42, 0.4);\n}\n\n:root[data-theme='dark'] {\n --m-bg: #111418;\n --m-bg-raised: #1a1f26;\n --m-bg-input: #20262e;\n --m-border: #2b333d;\n --m-text: #e8eaed;\n --m-text-secondary: #9aa3ad;\n --m-text-tertiary: #6b747f;\n --m-accent: #4f8cff;\n --m-accent-soft: rgba(79, 140, 255, 0.14);\n --m-danger: #e5534b;\n --m-success: #3fb68b;\n --m-radius: 12px;\n --m-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n --m-shadow-raise: 0 6px 20px rgba(0, 0, 0, 0.4);\n --m-backdrop: rgba(0, 0, 0, 0.55);\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n height: 100%;\n background: var(--m-bg);\n color: var(--m-text);\n font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Noto Sans SC', 'Segoe UI', sans-serif;\n font-size: 15px;\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n}\n\n/* The app fills exactly one viewport: the page itself never scrolls, each\n view owns its scroll region (chat history, session lists) and the chat\n composer stays pinned to the bottom of the screen. */\n#root {\n height: 100dvh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.mobile {\n min-height: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* \u2500\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-header {\n position: sticky;\n top: 0;\n z-index: 10;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px 10px;\n background: color-mix(in srgb, var(--m-bg) 88%, transparent);\n backdrop-filter: blur(10px);\n border-bottom: 1px solid var(--m-border);\n}\n\n.mobile-title {\n margin: 0;\n font-size: 17px;\n font-weight: 600;\n}\n\n.mobile-titleInline {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mobile-back {\n flex: none;\n width: 32px;\n height: 32px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--m-text);\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n transition: background-color 0.12s ease, box-shadow 0.12s ease;\n}\n\n.mobile-back:active {\n background: var(--m-bg-raised);\n}\n\n.mobile-back:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n.mobile-theme-toggle {\n flex: none;\n margin-left: auto;\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--m-text-secondary);\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;\n}\n\n.mobile-theme-toggle:active {\n background: var(--m-bg-raised);\n}\n\n.mobile-theme-toggle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n.mobile-theme-toggle svg {\n width: 18px;\n height: 18px;\n}\n\n/* \u2500\u2500 lists \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: 8px 0 calc(env(safe-area-inset-bottom, 0px) + 16px);\n}\n\n.mobile-row {\n display: flex;\n align-items: center;\n gap: 10px;\n width: calc(100% - 24px);\n margin: 0 12px 8px;\n padding: 13px 14px;\n border: 1px solid var(--m-border);\n border-radius: 14px;\n background: var(--m-bg-raised);\n box-shadow: var(--m-shadow);\n color: var(--m-text);\n text-align: left;\n cursor: pointer;\n}\n\n.mobile-row:active {\n background: color-mix(in srgb, var(--m-text) 5%, var(--m-bg-raised));\n}\n\n.mobile-rowMain {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n\n.mobile-rowTitle {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 15px;\n}\n\n.mobile-rowMeta {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n.mobile-chevron {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 18px;\n}\n\n.mobile-live {\n margin-left: 6px;\n color: var(--m-success);\n font-size: 10px;\n vertical-align: middle;\n}\n\n/* \u2500\u2500 empty / error states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px;\n}\n\n.mobile-muted {\n margin: 0;\n color: var(--m-text-tertiary);\n}\n\n.mobile-error {\n margin: 0;\n color: var(--m-danger);\n}\n\n.mobile-pad {\n padding: 8px 16px;\n}\n\n/* \u2500\u2500 buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-button {\n height: 38px;\n padding: 0 18px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: var(--m-bg-raised);\n color: var(--m-text);\n font-size: 14px;\n cursor: pointer;\n}\n\n.mobile-button:disabled {\n opacity: 0.5;\n}\n\n.mobile-block {\n display: block;\n width: calc(100% - 32px);\n margin: 4px 16px;\n}\n\n/* Primary action (create session): accent fill, readable on both themes. */\n.mobile-new {\n display: block;\n width: 100%;\n height: 44px;\n border: none;\n border-radius: 12px;\n background: linear-gradient(135deg, var(--m-accent) 0%, #4c8dff 100%);\n color: #fff;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(47, 111, 237, 0.3);\n}\n\n.mobile-new:active {\n opacity: 0.88;\n}\n\n.mobile-new:disabled {\n opacity: 0.6;\n}\n\n.mobile-hint {\n display: block;\n margin-top: 2px;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 chat \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.chat-scroll {\n flex: 1;\n overflow-y: auto;\n padding: 12px 14px calc(env(safe-area-inset-bottom, 0px) + 12px);\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.chat-msg {\n max-width: 88%;\n padding: 10px 12px;\n border-radius: var(--m-radius);\n font-size: 14.5px;\n line-height: 1.55;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.chat-msg-user {\n align-self: flex-end;\n background: linear-gradient(135deg, var(--m-accent) 0%, #4c8dff 100%);\n color: #fff;\n border-bottom-right-radius: 4px;\n box-shadow: 0 1px 3px rgba(47, 111, 237, 0.28);\n}\n\n.chat-msg-assistant {\n align-self: flex-start;\n background: var(--m-bg-raised);\n border: 1px solid var(--m-border);\n border-bottom-left-radius: 4px;\n box-shadow: var(--m-shadow);\n}\n\n.chat-msg-pending .chat-msg-text::after {\n content: '\u258D';\n animation: chat-blink 1s steps(2) infinite;\n}\n\n@keyframes chat-blink {\n 50% {\n opacity: 0;\n }\n}\n\n.chat-msg-failed {\n border-color: var(--m-danger);\n}\n\n.chat-msg-failtag {\n display: inline-block;\n margin-top: 6px;\n padding: 1px 8px;\n border: 1px solid var(--m-danger);\n border-radius: 999px;\n color: var(--m-danger);\n font-size: 11px;\n}\n\n.chat-msg-time {\n display: block;\n margin-top: 4px;\n color: var(--m-text-tertiary);\n font-size: 10.5px;\n line-height: 1.2;\n}\n\n.chat-msg-toggle {\n display: block;\n margin-top: 8px;\n padding: 4px 0;\n border: none;\n background: transparent;\n color: var(--m-accent);\n font-size: 13px;\n cursor: pointer;\n}\n\n/* \u2500\u2500 message disclosures (reasoning / tools) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat-disclosure {\n margin-bottom: 8px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: color-mix(in srgb, var(--m-bg) 55%, transparent);\n overflow: hidden;\n}\n\n.chat-disclosure-head {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 7px 10px;\n border: none;\n background: transparent;\n color: var(--m-text);\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.chat-disclosure-caret {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 14px;\n line-height: 1;\n transition: transform 0.15s ease;\n}\n\n.chat-disclosure-open .chat-disclosure-caret {\n transform: rotate(90deg);\n}\n\n.chat-disclosure-label {\n flex: none;\n color: var(--m-text-secondary);\n}\n\n.chat-reasoning[data-pending] .chat-disclosure-label {\n color: var(--m-accent);\n}\n\n.chat-disclosure-summary {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n.chat-disclosure-count {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 11px;\n}\n\n.chat-disclosure-body {\n padding: 8px 10px 10px;\n border-top: 1px dashed var(--m-border);\n color: var(--m-text-secondary);\n font-size: 12.5px;\n line-height: 1.6;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n max-height: 40vh;\n overflow-y: auto;\n}\n\n.chat-tools-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.chat-tool-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.chat-tool-name {\n color: var(--m-text);\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.chat-tool-args {\n margin: 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--m-bg-input);\n color: var(--m-text-secondary);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n}\n\n/* \u2500\u2500 composer toolbar (model / permission chips) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat-tools {\n display: flex;\n gap: 8px;\n padding: 6px 12px 0;\n}\n\n.chat-chip {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--m-border);\n border-radius: 999px;\n background: var(--m-bg-raised);\n box-shadow: var(--m-shadow);\n color: var(--m-text);\n font-size: 12px;\n cursor: pointer;\n}\n\n.chat-chip:active {\n border-color: var(--m-accent);\n background: var(--m-accent-soft);\n}\n\n.chat-chip-label {\n flex: none;\n color: var(--m-text-tertiary);\n}\n\n.chat-chip-value {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.chat-chip-chevron {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 14px;\n}\n\n/* \u2500\u2500 bottom sheets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.sheet-backdrop {\n position: fixed;\n inset: 0;\n z-index: 50;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n background: var(--m-backdrop);\n animation: sheet-fade 0.18s ease;\n}\n\n@keyframes sheet-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.sheet {\n max-height: 74dvh;\n display: flex;\n flex-direction: column;\n background: var(--m-bg-raised);\n border: 1px solid var(--m-border);\n border-bottom: none;\n border-radius: 16px 16px 0 0;\n box-shadow: var(--m-shadow-raise);\n padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);\n animation: sheet-up 0.22s ease;\n}\n\n@keyframes sheet-up {\n from {\n transform: translateY(40px);\n opacity: 0.6;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n.sheet-handle {\n flex: none;\n align-self: center;\n width: 36px;\n height: 4px;\n margin: 8px 0 4px;\n border-radius: 999px;\n background: var(--m-border);\n}\n\n.sheet-title {\n flex: none;\n padding: 6px 16px 10px;\n font-size: 15px;\n font-weight: 600;\n}\n\n.sheet-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 12px 8px;\n}\n\n.sheet-status {\n padding: 18px 8px;\n color: var(--m-text-tertiary);\n font-size: 13px;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.sheet-status-error {\n color: var(--m-danger);\n}\n\n.sheet-hint {\n margin: 0;\n color: var(--m-text-secondary);\n font-size: 12px;\n line-height: 1.5;\n max-width: 32em;\n}\n\n.sheet-error {\n margin: 0 0 8px;\n color: var(--m-danger);\n font-size: 12.5px;\n}\n\n.sheet-section {\n margin-bottom: 14px;\n}\n\n.sheet-section-title {\n padding: 4px 8px 6px;\n color: var(--m-text-tertiary);\n font-size: 12px;\n font-weight: 600;\n}\n\n.sheet-option {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 10px 12px;\n border: 1px solid transparent;\n border-radius: 10px;\n background: transparent;\n color: var(--m-text);\n text-align: left;\n cursor: pointer;\n}\n\n.sheet-option:active {\n background: var(--m-bg-input);\n}\n\n.sheet-option-selected {\n border-color: var(--m-accent);\n background: color-mix(in srgb, var(--m-accent) 12%, transparent);\n}\n\n.sheet-option:disabled {\n opacity: 0.5;\n}\n\n.sheet-option-copy {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sheet-option-title {\n font-size: 14px;\n}\n\n.sheet-option-desc {\n color: var(--m-text-tertiary);\n font-size: 12px;\n line-height: 1.4;\n}\n\n.sheet-option-check {\n flex: none;\n color: var(--m-accent);\n font-size: 15px;\n font-weight: 700;\n}\n\n.sheet-confirm-desc {\n margin: 0 4px 14px;\n color: var(--m-text-secondary);\n font-size: 13px;\n line-height: 1.6;\n}\n\n.sheet-confirm-actions {\n display: flex;\n gap: 10px;\n padding: 4px;\n}\n\n.sheet-confirm-actions .mobile-button {\n flex: 1;\n}\n\n.sheet-confirm-danger {\n flex: 1;\n height: 38px;\n border: none;\n border-radius: 10px;\n background: var(--m-danger);\n color: #fff;\n font-size: 14px;\n cursor: pointer;\n}\n\n.sheet-confirm-danger:disabled {\n opacity: 0.5;\n}\n\n.chat-meta {\n margin-top: 4px;\n color: var(--m-text-tertiary);\n font-size: 11px;\n}\n\n.chat-typing {\n color: var(--m-text-tertiary);\n font-size: 13px;\n padding: 4px 2px;\n}\n\n.chat-inputbar {\n display: flex;\n gap: 8px;\n padding: 10px 12px calc(env(safe-area-inset-bottom, 0px) + 10px);\n border-top: 1px solid var(--m-border);\n background: var(--m-bg);\n}\n\n.chat-input {\n flex: 1;\n min-width: 0;\n min-height: 40px;\n max-height: 120px;\n /* Grow with the content (still bounded by min/max-height); browsers\n without field-sizing support ignore this line and keep the old size. */\n field-sizing: content;\n padding: 9px 12px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: var(--m-bg-input);\n color: var(--m-text);\n font: inherit;\n font-size: 14.5px;\n resize: none;\n outline: none;\n}\n\n.chat-input:focus {\n border-color: var(--m-accent);\n}\n\n.chat-send {\n flex: none;\n align-self: flex-end;\n height: 40px;\n padding: 0 16px;\n border: none;\n border-radius: 10px;\n background: var(--m-accent);\n color: #fff;\n font-size: 14px;\n cursor: pointer;\n}\n\n.chat-send:disabled {\n opacity: 0.5;\n}\n\n.chat-load-older {\n align-self: center;\n margin: 4px 0;\n border: none;\n background: transparent;\n color: var(--m-text-secondary);\n font-size: 13px;\n cursor: pointer;\n padding: 6px 10px;\n}\n\n/* Touch surfaces have no hover, so keyboard focus must be fully visible.\n One shared ring keeps every interactive control consistent and readable\n on both palettes. */\n.mobile-row:focus-visible,\n.mobile-button:focus-visible,\n.mobile-new:focus-visible,\n.chat-send:focus-visible,\n.chat-chip:focus-visible,\n.chat-msg-toggle:focus-visible,\n.chat-load-older:focus-visible,\n.chat-disclosure-head:focus-visible,\n.sheet-option:focus-visible,\n.sheet-confirm-danger:focus-visible,\n.chat-input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n/* Accessibility: collapse every interaction/enter animation for\n reduced-motion users; surface layout itself stays static. */\n@media (prefers-reduced-motion: reduce) {\n .mobile-theme-toggle,\n .mobile-back,\n .mobile-row,\n .mobile-button,\n .mobile-new,\n .chat-send,\n .chat-chip,\n .chat-msg-toggle,\n .chat-load-older,\n .chat-disclosure-head,\n .chat-disclosure-caret,\n .chat-msg-pending .chat-msg-text::after,\n .sheet-backdrop,\n .sheet,\n .sheet-option,\n .sheet-confirm-danger,\n .chat-input {\n animation: none;\n transition: none;\n }\n}\n";
|
|
5
|
+
export declare const mobileCss = "/* Mobile surface chrome: standalone stylesheet (no main-UI design tokens \u2014\n this page boots without the shell). Light-first palette; the dark palette\n applies only when the header toggle sets [data-theme='dark']. */\n\n:root {\n --m-bg: #f3f5f9;\n --m-bg-raised: #ffffff;\n --m-bg-input: #ffffff;\n --m-border: #dfe4ec;\n --m-text: #1c2333;\n --m-text-secondary: #4d5768;\n --m-text-tertiary: #8b95a7;\n --m-accent: #2f6fed;\n --m-accent-soft: rgba(47, 111, 237, 0.12);\n --m-danger: #d64545;\n --m-success: #1e9e6a;\n --m-radius: 12px;\n --m-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);\n --m-shadow-raise: 0 6px 20px rgba(16, 24, 40, 0.1);\n --m-backdrop: rgba(15, 23, 42, 0.4);\n}\n\n:root[data-theme='dark'] {\n --m-bg: #111418;\n --m-bg-raised: #1a1f26;\n --m-bg-input: #20262e;\n --m-border: #2b333d;\n --m-text: #e8eaed;\n --m-text-secondary: #9aa3ad;\n --m-text-tertiary: #6b747f;\n --m-accent: #4f8cff;\n --m-accent-soft: rgba(79, 140, 255, 0.14);\n --m-danger: #e5534b;\n --m-success: #3fb68b;\n --m-radius: 12px;\n --m-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n --m-shadow-raise: 0 6px 20px rgba(0, 0, 0, 0.4);\n --m-backdrop: rgba(0, 0, 0, 0.55);\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n margin: 0;\n padding: 0;\n height: 100%;\n background: var(--m-bg);\n color: var(--m-text);\n font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Noto Sans SC', 'Segoe UI', sans-serif;\n font-size: 15px;\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n}\n\n/* The app fills exactly one viewport: the page itself never scrolls, each\n view owns its scroll region (chat history, session lists) and the chat\n composer stays pinned to the bottom of the screen. */\n#root {\n height: 100dvh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.mobile {\n min-height: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* \u2500\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-header {\n position: sticky;\n top: 0;\n z-index: 10;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px 10px;\n background: color-mix(in srgb, var(--m-bg) 88%, transparent);\n backdrop-filter: blur(10px);\n border-bottom: 1px solid var(--m-border);\n}\n\n.mobile-title {\n margin: 0;\n font-size: 17px;\n font-weight: 600;\n}\n\n.mobile-titleInline {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mobile-back {\n flex: none;\n width: 32px;\n height: 32px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--m-text);\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n transition: background-color 0.12s ease, box-shadow 0.12s ease;\n}\n\n.mobile-back:active {\n background: var(--m-bg-raised);\n}\n\n.mobile-back:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n.mobile-theme-toggle {\n flex: none;\n margin-left: auto;\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--m-text-secondary);\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;\n}\n\n.mobile-theme-toggle:active {\n background: var(--m-bg-raised);\n}\n\n.mobile-theme-toggle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n.mobile-theme-toggle svg {\n width: 18px;\n height: 18px;\n}\n\n/* \u2500\u2500 lists \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: 8px 0 calc(env(safe-area-inset-bottom, 0px) + 16px);\n}\n\n.mobile-row {\n display: flex;\n align-items: center;\n gap: 10px;\n width: calc(100% - 24px);\n margin: 0 12px 8px;\n padding: 13px 14px;\n border: 1px solid var(--m-border);\n border-radius: 14px;\n background: var(--m-bg-raised);\n box-shadow: var(--m-shadow);\n color: var(--m-text);\n text-align: left;\n cursor: pointer;\n}\n\n.mobile-row:active {\n background: color-mix(in srgb, var(--m-text) 5%, var(--m-bg-raised));\n}\n\n.mobile-rowMain {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n\n.mobile-rowTitle {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 15px;\n}\n\n.mobile-rowMeta {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n.mobile-chevron {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 18px;\n}\n\n.mobile-live {\n margin-left: 6px;\n color: var(--m-success);\n font-size: 10px;\n vertical-align: middle;\n}\n\n/* \u2500\u2500 empty / error states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px;\n}\n\n.mobile-muted {\n margin: 0;\n color: var(--m-text-tertiary);\n}\n\n.mobile-error {\n margin: 0;\n color: var(--m-danger);\n}\n\n.mobile-pad {\n padding: 8px 16px;\n}\n\n/* \u2500\u2500 buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.mobile-button {\n height: 38px;\n padding: 0 18px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: var(--m-bg-raised);\n color: var(--m-text);\n font-size: 14px;\n cursor: pointer;\n}\n\n.mobile-button:disabled {\n opacity: 0.5;\n}\n\n.mobile-block {\n display: block;\n width: calc(100% - 32px);\n margin: 4px 16px;\n}\n\n/* Primary action (create session): accent fill, readable on both themes. */\n.mobile-new {\n display: block;\n width: 100%;\n height: 44px;\n border: none;\n border-radius: 12px;\n background: linear-gradient(135deg, var(--m-accent) 0%, #4c8dff 100%);\n color: #fff;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(47, 111, 237, 0.3);\n}\n\n.mobile-new:active {\n opacity: 0.88;\n}\n\n.mobile-new:disabled {\n opacity: 0.6;\n}\n\n.mobile-hint {\n display: block;\n margin-top: 2px;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 chat \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.chat-scroll {\n flex: 1;\n overflow-y: auto;\n padding: 12px 14px calc(env(safe-area-inset-bottom, 0px) + 12px);\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.chat-msg {\n max-width: 88%;\n padding: 10px 12px;\n border-radius: var(--m-radius);\n font-size: 14.5px;\n line-height: 1.55;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.chat-msg-user {\n align-self: flex-end;\n background: linear-gradient(135deg, var(--m-accent) 0%, #4c8dff 100%);\n color: #fff;\n border-bottom-right-radius: 4px;\n box-shadow: 0 1px 3px rgba(47, 111, 237, 0.28);\n}\n\n.chat-msg-assistant {\n align-self: flex-start;\n background: var(--m-bg-raised);\n border: 1px solid var(--m-border);\n border-bottom-left-radius: 4px;\n box-shadow: var(--m-shadow);\n}\n\n.chat-msg-pending .chat-msg-text::after {\n content: '\u258D';\n animation: chat-blink 1s steps(2) infinite;\n}\n\n@keyframes chat-blink {\n 50% {\n opacity: 0;\n }\n}\n\n.chat-msg-failed {\n border-color: var(--m-danger);\n}\n\n.chat-msg-failtag {\n display: inline-block;\n margin-top: 6px;\n padding: 1px 8px;\n border: 1px solid var(--m-danger);\n border-radius: 999px;\n color: var(--m-danger);\n font-size: 11px;\n}\n\n.chat-msg-time {\n display: block;\n margin-top: 4px;\n color: var(--m-text-tertiary);\n font-size: 10.5px;\n line-height: 1.2;\n}\n\n.chat-msg-toggle {\n display: block;\n margin-top: 8px;\n padding: 4px 0;\n border: none;\n background: transparent;\n color: var(--m-accent);\n font-size: 13px;\n cursor: pointer;\n}\n\n/* Assistant markdown content (GFM subset; renderer in markdown.ts). */\n.chat-md {\n white-space: normal;\n}\n.chat-md-collapsed {\n max-height: 45vh;\n overflow: hidden;\n position: relative;\n}\n.chat-md-collapsed::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n height: 28px;\n background: linear-gradient(transparent, var(--m-bg-raised));\n pointer-events: none;\n}\n.chat-md-body p { margin: 0 0 8px; }\n.chat-md-body p:last-child { margin-bottom: 0; }\n.chat-md-body h1, .chat-md-body h2, .chat-md-body h3,\n.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 {\n margin: 12px 0 6px;\n font-weight: 650;\n line-height: 1.3;\n}\n.chat-md-body h1 { font-size: 1.35em; }\n.chat-md-body h2 { font-size: 1.25em; }\n.chat-md-body h3 { font-size: 1.15em; }\n.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 { font-size: 1.05em; }\n.chat-md-body code {\n font-family: var(--m-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);\n font-size: 0.9em;\n background: var(--m-accent-soft);\n padding: 1px 5px;\n border-radius: 4px;\n}\n.chat-md-body pre {\n margin: 8px 0;\n padding: 10px;\n background: #101726;\n color: #dbe4f5;\n border-radius: 8px;\n overflow-x: auto;\n font-size: 12.5px;\n line-height: 1.5;\n}\n.chat-md-body pre code {\n background: transparent;\n padding: 0;\n color: inherit;\n}\n.chat-md-body ul, .chat-md-body ol { margin: 4px 0 8px; padding-left: 22px; }\n.chat-md-body li { margin: 2px 0; }\n.chat-md-body blockquote {\n margin: 8px 0;\n padding: 4px 10px;\n border-left: 3px solid var(--m-accent);\n background: var(--m-accent-soft);\n border-radius: 0 6px 6px 0;\n color: var(--m-text-secondary);\n}\n.chat-md-body table {\n margin: 8px 0;\n border-collapse: collapse;\n display: block;\n overflow-x: auto;\n font-size: 13px;\n}\n.chat-md-body th, .chat-md-body td {\n border: 1px solid var(--m-border);\n padding: 4px 8px;\n}\n.chat-md-body th { background: var(--m-accent-soft); }\n.chat-md-body a { color: var(--m-accent); }\n.chat-md-body hr { border: none; border-top: 1px solid var(--m-border); margin: 10px 0; }\n.chat-md-body img { max-width: 100%; border-radius: 8px; }\n\n/* \u2500\u2500 message disclosures (reasoning / tools) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat-disclosure {\n margin-bottom: 8px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: color-mix(in srgb, var(--m-bg) 55%, transparent);\n overflow: hidden;\n}\n\n.chat-disclosure-head {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 7px 10px;\n border: none;\n background: transparent;\n color: var(--m-text);\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.chat-disclosure-caret {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 14px;\n line-height: 1;\n transition: transform 0.15s ease;\n}\n\n.chat-disclosure-open .chat-disclosure-caret {\n transform: rotate(90deg);\n}\n\n.chat-disclosure-label {\n flex: none;\n color: var(--m-text-secondary);\n}\n\n.chat-reasoning[data-pending] .chat-disclosure-label {\n color: var(--m-accent);\n}\n\n.chat-disclosure-summary {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--m-text-tertiary);\n font-size: 12px;\n}\n\n.chat-disclosure-count {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 11px;\n}\n\n.chat-disclosure-body {\n padding: 8px 10px 10px;\n border-top: 1px dashed var(--m-border);\n color: var(--m-text-secondary);\n font-size: 12.5px;\n line-height: 1.6;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n max-height: 40vh;\n overflow-y: auto;\n}\n\n.chat-tools-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.chat-tool-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.chat-tool-name {\n color: var(--m-text);\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.chat-tool-args {\n margin: 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--m-bg-input);\n color: var(--m-text-secondary);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n}\n\n/* \u2500\u2500 composer toolbar (model / permission chips) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat-tools {\n display: flex;\n gap: 8px;\n padding: 6px 12px 0;\n}\n\n.chat-chip {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 10px;\n border: 1px solid var(--m-border);\n border-radius: 999px;\n background: var(--m-bg-raised);\n box-shadow: var(--m-shadow);\n color: var(--m-text);\n font-size: 12px;\n cursor: pointer;\n}\n\n.chat-chip:active {\n border-color: var(--m-accent);\n background: var(--m-accent-soft);\n}\n\n.chat-chip-label {\n flex: none;\n color: var(--m-text-tertiary);\n}\n\n.chat-chip-value {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.chat-chip-chevron {\n flex: none;\n color: var(--m-text-tertiary);\n font-size: 14px;\n}\n\n/* \u2500\u2500 bottom sheets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.sheet-backdrop {\n position: fixed;\n inset: 0;\n z-index: 50;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n background: var(--m-backdrop);\n animation: sheet-fade 0.18s ease;\n}\n\n@keyframes sheet-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.sheet {\n max-height: 74dvh;\n display: flex;\n flex-direction: column;\n background: var(--m-bg-raised);\n border: 1px solid var(--m-border);\n border-bottom: none;\n border-radius: 16px 16px 0 0;\n box-shadow: var(--m-shadow-raise);\n padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);\n animation: sheet-up 0.22s ease;\n}\n\n@keyframes sheet-up {\n from {\n transform: translateY(40px);\n opacity: 0.6;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n.sheet-handle {\n flex: none;\n align-self: center;\n width: 36px;\n height: 4px;\n margin: 8px 0 4px;\n border-radius: 999px;\n background: var(--m-border);\n}\n\n.sheet-title {\n flex: none;\n padding: 6px 16px 10px;\n font-size: 15px;\n font-weight: 600;\n}\n\n.sheet-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 12px 8px;\n}\n\n.sheet-status {\n padding: 18px 8px;\n color: var(--m-text-tertiary);\n font-size: 13px;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.sheet-status-error {\n color: var(--m-danger);\n}\n\n.sheet-hint {\n margin: 0;\n color: var(--m-text-secondary);\n font-size: 12px;\n line-height: 1.5;\n max-width: 32em;\n}\n\n.sheet-error {\n margin: 0 0 8px;\n color: var(--m-danger);\n font-size: 12.5px;\n}\n\n.sheet-section {\n margin-bottom: 14px;\n}\n\n.sheet-section-title {\n padding: 4px 8px 6px;\n color: var(--m-text-tertiary);\n font-size: 12px;\n font-weight: 600;\n}\n\n.sheet-option {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 10px 12px;\n border: 1px solid transparent;\n border-radius: 10px;\n background: transparent;\n color: var(--m-text);\n text-align: left;\n cursor: pointer;\n}\n\n.sheet-option:active {\n background: var(--m-bg-input);\n}\n\n.sheet-option-selected {\n border-color: var(--m-accent);\n background: color-mix(in srgb, var(--m-accent) 12%, transparent);\n}\n\n.sheet-option:disabled {\n opacity: 0.5;\n}\n\n.sheet-option-copy {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sheet-option-title {\n font-size: 14px;\n}\n\n.sheet-option-desc {\n color: var(--m-text-tertiary);\n font-size: 12px;\n line-height: 1.4;\n}\n\n.sheet-option-check {\n flex: none;\n color: var(--m-accent);\n font-size: 15px;\n font-weight: 700;\n}\n\n.sheet-confirm-desc {\n margin: 0 4px 14px;\n color: var(--m-text-secondary);\n font-size: 13px;\n line-height: 1.6;\n}\n\n.sheet-confirm-actions {\n display: flex;\n gap: 10px;\n padding: 4px;\n}\n\n.sheet-confirm-actions .mobile-button {\n flex: 1;\n}\n\n.sheet-confirm-danger {\n flex: 1;\n height: 38px;\n border: none;\n border-radius: 10px;\n background: var(--m-danger);\n color: #fff;\n font-size: 14px;\n cursor: pointer;\n}\n\n.sheet-confirm-danger:disabled {\n opacity: 0.5;\n}\n\n.chat-meta {\n margin-top: 4px;\n color: var(--m-text-tertiary);\n font-size: 11px;\n}\n\n.chat-typing {\n color: var(--m-text-tertiary);\n font-size: 13px;\n padding: 4px 2px;\n}\n\n.chat-inputbar {\n display: flex;\n gap: 8px;\n padding: 10px 12px calc(env(safe-area-inset-bottom, 0px) + 10px);\n border-top: 1px solid var(--m-border);\n background: var(--m-bg);\n}\n\n.chat-input {\n flex: 1;\n min-width: 0;\n min-height: 40px;\n max-height: 120px;\n /* Grow with the content (still bounded by min/max-height); browsers\n without field-sizing support ignore this line and keep the old size. */\n field-sizing: content;\n padding: 9px 12px;\n border: 1px solid var(--m-border);\n border-radius: 10px;\n background: var(--m-bg-input);\n color: var(--m-text);\n font: inherit;\n font-size: 14.5px;\n resize: none;\n outline: none;\n}\n\n.chat-input:focus {\n border-color: var(--m-accent);\n}\n\n.chat-send {\n flex: none;\n align-self: flex-end;\n height: 40px;\n padding: 0 16px;\n border: none;\n border-radius: 10px;\n background: var(--m-accent);\n color: #fff;\n font-size: 14px;\n cursor: pointer;\n}\n\n.chat-send:disabled {\n opacity: 0.5;\n}\n\n.chat-load-older {\n align-self: center;\n margin: 4px 0;\n border: none;\n background: transparent;\n color: var(--m-text-secondary);\n font-size: 13px;\n cursor: pointer;\n padding: 6px 10px;\n}\n\n/* Touch surfaces have no hover, so keyboard focus must be fully visible.\n One shared ring keeps every interactive control consistent and readable\n on both palettes. */\n.mobile-row:focus-visible,\n.mobile-button:focus-visible,\n.mobile-new:focus-visible,\n.chat-send:focus-visible,\n.chat-chip:focus-visible,\n.chat-msg-toggle:focus-visible,\n.chat-load-older:focus-visible,\n.chat-disclosure-head:focus-visible,\n.sheet-option:focus-visible,\n.sheet-confirm-danger:focus-visible,\n.sheet-toggle-switch:focus-visible,\n.chat-input:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);\n}\n\n/* \u2500\u2500 chat context usage chip (chat-tools row) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.chat-context {\n flex: none;\n align-self: center;\n padding: 2px 10px;\n border: 1px solid var(--m-border);\n border-radius: 999px;\n background: color-mix(in srgb, var(--m-text-tertiary) 10%, transparent);\n color: var(--m-text-tertiary);\n font-size: 11px;\n line-height: 1.6;\n white-space: nowrap;\n}\n\n.chat-context-warn {\n border-color: var(--m-danger);\n background: color-mix(in srgb, var(--m-danger) 14%, transparent);\n color: var(--m-danger);\n}\n\n/* \u2500\u2500 display sheet toggle rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.sheet-toggle-row {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 8px;\n border-bottom: 1px solid var(--m-border);\n}\n\n.sheet-toggle-row:last-child {\n border-bottom: none;\n}\n\n.sheet-toggle-copy {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sheet-toggle-title {\n font-size: 14px;\n}\n\n.sheet-toggle-desc {\n color: var(--m-text-tertiary);\n font-size: 12px;\n line-height: 1.4;\n}\n\n.sheet-toggle-switch {\n position: relative;\n flex: none;\n width: 44px;\n height: 26px;\n border: none;\n border-radius: 999px;\n background: var(--m-border);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.sheet-toggle-switch-on {\n background: var(--m-accent);\n}\n\n.sheet-toggle-switch-knob {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 1px 2px rgba(16, 24, 40, 0.2);\n transition: transform 0.15s ease;\n}\n\n.sheet-toggle-switch-on .sheet-toggle-switch-knob {\n transform: translateX(18px);\n}\n\n/* Accessibility: collapse every interaction/enter animation for\n reduced-motion users; surface layout itself stays static. */\n@media (prefers-reduced-motion: reduce) {\n .mobile-theme-toggle,\n .mobile-back,\n .mobile-row,\n .mobile-button,\n .mobile-new,\n .chat-send,\n .chat-chip,\n .chat-msg-toggle,\n .chat-load-older,\n .chat-disclosure-head,\n .chat-disclosure-caret,\n .chat-msg-pending .chat-msg-text::after,\n .sheet-backdrop,\n .sheet,\n .sheet-option,\n .sheet-confirm-danger,\n .sheet-toggle-switch,\n .sheet-toggle-switch-knob,\n .chat-input {\n animation: none;\n transition: none;\n }\n}\n";
|
|
6
6
|
//# sourceMappingURL=mobile-styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-styles.d.ts","sourceRoot":"","sources":["../../../src/mobile/mobile-styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"mobile-styles.d.ts","sourceRoot":"","sources":["../../../src/mobile/mobile-styles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,SAAS,g6tBA0/BrB,CAAA"}
|
|
@@ -393,6 +393,85 @@ body {
|
|
|
393
393
|
cursor: pointer;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
/* Assistant markdown content (GFM subset; renderer in markdown.ts). */
|
|
397
|
+
.chat-md {
|
|
398
|
+
white-space: normal;
|
|
399
|
+
}
|
|
400
|
+
.chat-md-collapsed {
|
|
401
|
+
max-height: 45vh;
|
|
402
|
+
overflow: hidden;
|
|
403
|
+
position: relative;
|
|
404
|
+
}
|
|
405
|
+
.chat-md-collapsed::after {
|
|
406
|
+
content: '';
|
|
407
|
+
position: absolute;
|
|
408
|
+
left: 0;
|
|
409
|
+
right: 0;
|
|
410
|
+
bottom: 0;
|
|
411
|
+
height: 28px;
|
|
412
|
+
background: linear-gradient(transparent, var(--m-bg-raised));
|
|
413
|
+
pointer-events: none;
|
|
414
|
+
}
|
|
415
|
+
.chat-md-body p { margin: 0 0 8px; }
|
|
416
|
+
.chat-md-body p:last-child { margin-bottom: 0; }
|
|
417
|
+
.chat-md-body h1, .chat-md-body h2, .chat-md-body h3,
|
|
418
|
+
.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 {
|
|
419
|
+
margin: 12px 0 6px;
|
|
420
|
+
font-weight: 650;
|
|
421
|
+
line-height: 1.3;
|
|
422
|
+
}
|
|
423
|
+
.chat-md-body h1 { font-size: 1.35em; }
|
|
424
|
+
.chat-md-body h2 { font-size: 1.25em; }
|
|
425
|
+
.chat-md-body h3 { font-size: 1.15em; }
|
|
426
|
+
.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 { font-size: 1.05em; }
|
|
427
|
+
.chat-md-body code {
|
|
428
|
+
font-family: var(--m-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
429
|
+
font-size: 0.9em;
|
|
430
|
+
background: var(--m-accent-soft);
|
|
431
|
+
padding: 1px 5px;
|
|
432
|
+
border-radius: 4px;
|
|
433
|
+
}
|
|
434
|
+
.chat-md-body pre {
|
|
435
|
+
margin: 8px 0;
|
|
436
|
+
padding: 10px;
|
|
437
|
+
background: #101726;
|
|
438
|
+
color: #dbe4f5;
|
|
439
|
+
border-radius: 8px;
|
|
440
|
+
overflow-x: auto;
|
|
441
|
+
font-size: 12.5px;
|
|
442
|
+
line-height: 1.5;
|
|
443
|
+
}
|
|
444
|
+
.chat-md-body pre code {
|
|
445
|
+
background: transparent;
|
|
446
|
+
padding: 0;
|
|
447
|
+
color: inherit;
|
|
448
|
+
}
|
|
449
|
+
.chat-md-body ul, .chat-md-body ol { margin: 4px 0 8px; padding-left: 22px; }
|
|
450
|
+
.chat-md-body li { margin: 2px 0; }
|
|
451
|
+
.chat-md-body blockquote {
|
|
452
|
+
margin: 8px 0;
|
|
453
|
+
padding: 4px 10px;
|
|
454
|
+
border-left: 3px solid var(--m-accent);
|
|
455
|
+
background: var(--m-accent-soft);
|
|
456
|
+
border-radius: 0 6px 6px 0;
|
|
457
|
+
color: var(--m-text-secondary);
|
|
458
|
+
}
|
|
459
|
+
.chat-md-body table {
|
|
460
|
+
margin: 8px 0;
|
|
461
|
+
border-collapse: collapse;
|
|
462
|
+
display: block;
|
|
463
|
+
overflow-x: auto;
|
|
464
|
+
font-size: 13px;
|
|
465
|
+
}
|
|
466
|
+
.chat-md-body th, .chat-md-body td {
|
|
467
|
+
border: 1px solid var(--m-border);
|
|
468
|
+
padding: 4px 8px;
|
|
469
|
+
}
|
|
470
|
+
.chat-md-body th { background: var(--m-accent-soft); }
|
|
471
|
+
.chat-md-body a { color: var(--m-accent); }
|
|
472
|
+
.chat-md-body hr { border: none; border-top: 1px solid var(--m-border); margin: 10px 0; }
|
|
473
|
+
.chat-md-body img { max-width: 100%; border-radius: 8px; }
|
|
474
|
+
|
|
396
475
|
/* ── message disclosures (reasoning / tools) ─────────────────────────── */
|
|
397
476
|
|
|
398
477
|
.chat-disclosure {
|
|
@@ -824,11 +903,97 @@ body {
|
|
|
824
903
|
.chat-disclosure-head:focus-visible,
|
|
825
904
|
.sheet-option:focus-visible,
|
|
826
905
|
.sheet-confirm-danger:focus-visible,
|
|
906
|
+
.sheet-toggle-switch:focus-visible,
|
|
827
907
|
.chat-input:focus-visible {
|
|
828
908
|
outline: none;
|
|
829
909
|
box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);
|
|
830
910
|
}
|
|
831
911
|
|
|
912
|
+
/* ── chat context usage chip (chat-tools row) ───────────────────────── */
|
|
913
|
+
|
|
914
|
+
.chat-context {
|
|
915
|
+
flex: none;
|
|
916
|
+
align-self: center;
|
|
917
|
+
padding: 2px 10px;
|
|
918
|
+
border: 1px solid var(--m-border);
|
|
919
|
+
border-radius: 999px;
|
|
920
|
+
background: color-mix(in srgb, var(--m-text-tertiary) 10%, transparent);
|
|
921
|
+
color: var(--m-text-tertiary);
|
|
922
|
+
font-size: 11px;
|
|
923
|
+
line-height: 1.6;
|
|
924
|
+
white-space: nowrap;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.chat-context-warn {
|
|
928
|
+
border-color: var(--m-danger);
|
|
929
|
+
background: color-mix(in srgb, var(--m-danger) 14%, transparent);
|
|
930
|
+
color: var(--m-danger);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* ── display sheet toggle rows ──────────────────────────────────────── */
|
|
934
|
+
|
|
935
|
+
.sheet-toggle-row {
|
|
936
|
+
display: flex;
|
|
937
|
+
align-items: center;
|
|
938
|
+
gap: 12px;
|
|
939
|
+
padding: 12px 8px;
|
|
940
|
+
border-bottom: 1px solid var(--m-border);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.sheet-toggle-row:last-child {
|
|
944
|
+
border-bottom: none;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.sheet-toggle-copy {
|
|
948
|
+
flex: 1;
|
|
949
|
+
min-width: 0;
|
|
950
|
+
display: flex;
|
|
951
|
+
flex-direction: column;
|
|
952
|
+
gap: 2px;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.sheet-toggle-title {
|
|
956
|
+
font-size: 14px;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.sheet-toggle-desc {
|
|
960
|
+
color: var(--m-text-tertiary);
|
|
961
|
+
font-size: 12px;
|
|
962
|
+
line-height: 1.4;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.sheet-toggle-switch {
|
|
966
|
+
position: relative;
|
|
967
|
+
flex: none;
|
|
968
|
+
width: 44px;
|
|
969
|
+
height: 26px;
|
|
970
|
+
border: none;
|
|
971
|
+
border-radius: 999px;
|
|
972
|
+
background: var(--m-border);
|
|
973
|
+
cursor: pointer;
|
|
974
|
+
transition: background-color 0.15s ease;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.sheet-toggle-switch-on {
|
|
978
|
+
background: var(--m-accent);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.sheet-toggle-switch-knob {
|
|
982
|
+
position: absolute;
|
|
983
|
+
top: 3px;
|
|
984
|
+
left: 3px;
|
|
985
|
+
width: 20px;
|
|
986
|
+
height: 20px;
|
|
987
|
+
border-radius: 50%;
|
|
988
|
+
background: #fff;
|
|
989
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.2);
|
|
990
|
+
transition: transform 0.15s ease;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.sheet-toggle-switch-on .sheet-toggle-switch-knob {
|
|
994
|
+
transform: translateX(18px);
|
|
995
|
+
}
|
|
996
|
+
|
|
832
997
|
/* Accessibility: collapse every interaction/enter animation for
|
|
833
998
|
reduced-motion users; surface layout itself stays static. */
|
|
834
999
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -848,6 +1013,8 @@ body {
|
|
|
848
1013
|
.sheet,
|
|
849
1014
|
.sheet-option,
|
|
850
1015
|
.sheet-confirm-danger,
|
|
1016
|
+
.sheet-toggle-switch,
|
|
1017
|
+
.sheet-toggle-switch-knob,
|
|
851
1018
|
.chat-input {
|
|
852
1019
|
animation: none;
|
|
853
1020
|
transition: none;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/ChatView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/ChatView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAKjE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGrC,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAA;IACpB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC3B,MAAM,IAAI,IAAI,CAAA;CACf;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAqCzC,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;CACrB;AAqCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,aAAa,+BAgW/D"}
|
|
@@ -11,11 +11,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
* - a toolbar above the composer carries the model (+ thinking effort) and
|
|
12
12
|
* permission pickers, both as bottom sheets.
|
|
13
13
|
*/
|
|
14
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
import { loadHistory, prompt } from "./App.js";
|
|
16
16
|
import { errorText, formatTime, staleHostHint } from "./App.js";
|
|
17
17
|
import { fetchMobilePreferences, models, selectModel, sendCommand } from "../api.js";
|
|
18
18
|
import { foldEvents } from "../messages.js";
|
|
19
|
+
import { renderMarkdown } from "../markdown.js";
|
|
19
20
|
import { ThemeToggle } from "../theme-toggle.js";
|
|
20
21
|
/**
|
|
21
22
|
* Hard cap on live events buffered while the initial history tail page is in
|
|
@@ -23,6 +24,31 @@ import { ThemeToggle } from "../theme-toggle.js";
|
|
|
23
24
|
* history tail re-pull closes the seam.
|
|
24
25
|
*/
|
|
25
26
|
export const MAX_TAIL_BUFFER_EVENTS = 500;
|
|
27
|
+
/** localStorage key for the tool-call display toggle (persisted on the /m origin). */
|
|
28
|
+
const SHOW_TOOL_CALLS_KEY = 'dsh.mobile.showToolCalls';
|
|
29
|
+
/** localStorage key for the injected-system-message display toggle. */
|
|
30
|
+
const SHOW_SYSTEM_MESSAGES_KEY = 'dsh.mobile.showSystemMessages';
|
|
31
|
+
/** Read a boolean from localStorage defensively; falls back to the default. */
|
|
32
|
+
function readStoredBoolean(key, fallback) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = localStorage.getItem(key);
|
|
35
|
+
if (raw === null)
|
|
36
|
+
return fallback;
|
|
37
|
+
return raw === '1' || raw.toLowerCase() === 'true';
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Persist a boolean toggle; storage failures are ignored (feature stays non-persistent). */
|
|
44
|
+
function writeStoredBoolean(key, value) {
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem(key, value ? '1' : '0');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* quota / privacy mode: non-persistent is acceptable */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
26
52
|
/** Extract the raw event from one history entry (the fold consumes events only). */
|
|
27
53
|
function eventOf(entry) {
|
|
28
54
|
return entry.event;
|
|
@@ -101,6 +127,10 @@ export function ChatView({ session, mux, onBack }) {
|
|
|
101
127
|
const [currentModel, setCurrentModel] = useState(undefined);
|
|
102
128
|
/** Which bottom sheet is open. */
|
|
103
129
|
const [sheet, setSheet] = useState(null);
|
|
130
|
+
/** Show tool-call disclosures (default on, persisted on the /m origin). */
|
|
131
|
+
const [showToolCalls, setShowToolCalls] = useState(() => readStoredBoolean(SHOW_TOOL_CALLS_KEY, true));
|
|
132
|
+
/** Show injected system messages (default off, persisted on the /m origin). */
|
|
133
|
+
const [showSystemMessages, setShowSystemMessages] = useState(() => readStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, false));
|
|
104
134
|
/**
|
|
105
135
|
* Composer preference from the plugin's host settings (default true keeps
|
|
106
136
|
* the legacy Enter-to-send behavior until the preference loads).
|
|
@@ -292,19 +322,47 @@ export function ChatView({ session, mux, onBack }) {
|
|
|
292
322
|
? undefined
|
|
293
323
|
: permissions.options.find(option => option.value === permissions.currentValue)?.name
|
|
294
324
|
?? displayName(permissions.currentValue);
|
|
295
|
-
|
|
325
|
+
// Context usage chip: the most recent assistant message carrying both usage and
|
|
326
|
+
// a positive context window drives the percentage. Scanned from the end so a
|
|
327
|
+
// newer answer (whose usage may be the last one reported) takes precedence.
|
|
328
|
+
const contextUsage = useMemo(() => {
|
|
329
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
330
|
+
const message = messages[index];
|
|
331
|
+
const usage = message.usage;
|
|
332
|
+
if (message.kind !== 'assistant' || usage === undefined)
|
|
333
|
+
continue;
|
|
334
|
+
const window = message.contextWindow;
|
|
335
|
+
if (window === undefined || window <= 0)
|
|
336
|
+
continue;
|
|
337
|
+
const tokens = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
338
|
+
const pct = Math.round(tokens / window * 100);
|
|
339
|
+
return { pct };
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}, [messages]);
|
|
343
|
+
return (_jsxs("div", { className: "chat", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("button", { type: "button", className: "mobile-back", "aria-label": "\u8FD4\u56DE", onClick: onBack, children: "\u2039" }), _jsx("h1", { className: "mobile-title mobile-titleInline", children: session.title }), _jsx(ThemeToggle, {})] }), error !== undefined && _jsx("p", { className: "mobile-error mobile-pad", children: error }), _jsxs("div", { className: "chat-scroll", ref: ref => { scrollRef.current = ref ?? undefined; }, children: [hasOlder && (_jsx("button", { type: "button", className: "chat-load-older", disabled: loading, onClick: () => { void loadOlder(); }, children: loading ? '加载中…' : '加载更早的消息' })), messages.map(message => (_jsx(MessageRow, { message: message, showToolCalls: showToolCalls, showSystemMessages: showSystemMessages }, message.id))), loading && messages.length === 0 && _jsx("p", { className: "chat-typing", children: "\u52A0\u8F7D\u4E2D\u2026" }), !loading && messages.length === 0 && _jsx("p", { className: "chat-typing", children: "\u8FD8\u6CA1\u6709\u6D88\u606F\uFF0C\u53D1\u4E00\u53E5\u8BDD\u5F00\u59CB\u5427" })] }), _jsxs("div", { className: "chat-tools", children: [_jsxs("button", { type: "button", className: "chat-chip", onClick: () => { setSheet('model'); }, "aria-haspopup": "dialog", children: [_jsx("span", { className: "chat-chip-label", children: "\u6A21\u578B" }), _jsx("span", { className: "chat-chip-value", children: modelLabel }), _jsx("span", { className: "chat-chip-chevron", "aria-hidden": true, children: "\u203A" })] }), _jsxs("button", { type: "button", className: "chat-chip", onClick: () => { setSheet('display'); }, "aria-haspopup": "dialog", children: [_jsx("span", { className: "chat-chip-label", children: "\u663E\u793A" }), _jsx("span", { className: "chat-chip-chevron", "aria-hidden": true, children: "\u203A" })] }), permissionLabel !== undefined && (_jsxs("button", { type: "button", className: "chat-chip", onClick: () => { setSheet('permission'); }, "aria-haspopup": "dialog", children: [_jsx("span", { className: "chat-chip-label", children: "\u6743\u9650" }), _jsx("span", { className: "chat-chip-value", children: permissionLabel }), _jsx("span", { className: "chat-chip-chevron", "aria-hidden": true, children: "\u203A" })] })), contextUsage !== undefined && (_jsxs("div", { className: "chat-context" + (contextUsage.pct >= 80 ? " chat-context-warn" : ""), children: ["\u4E0A\u4E0B\u6587 ", contextUsage.pct, "%"] }))] }), _jsxs("div", { className: "chat-inputbar", children: [_jsx("textarea", { className: "chat-input", rows: 1, value: input, placeholder: mobileEnterToSend ? '输入消息,Enter 发送…' : '输入消息,Enter 换行,点按钮发送…', enterKeyHint: mobileEnterToSend ? 'send' : 'enter', onChange: (event) => { setInput(event.target.value); }, onKeyDown: (event) => {
|
|
296
344
|
if (mobileEnterToSend && event.key === 'Enter' && !event.shiftKey) {
|
|
297
345
|
event.preventDefault();
|
|
298
346
|
void send();
|
|
299
347
|
}
|
|
300
348
|
} }), _jsx("button", { type: "button", className: "chat-send", disabled: sending || input.trim() === '', onClick: () => { void send(); }, children: sending ? '发送中…' : '发送' })] }), sheet === 'model' && (_jsx(ModelSheet, { sessionId: session.sessionId, current: currentModel, onCurrent: (selection) => { setCurrentModel(selection); }, onClose: () => { setSheet(null); } })), sheet === 'permission' && permissions !== undefined && (_jsx(PermissionSheet, { sessionId: session.sessionId, value: permissions, onChanged: (value) => {
|
|
301
349
|
setPermissions(previous => previous === undefined ? previous : { ...previous, currentValue: value });
|
|
302
|
-
}, onClose: () => { setSheet(null); } }))] }));
|
|
350
|
+
}, onClose: () => { setSheet(null); } })), sheet === 'display' && (_jsx(DisplaySheet, { showToolCalls: showToolCalls, showSystemMessages: showSystemMessages, onToolCalls: (value) => { setShowToolCalls(value); writeStoredBoolean(SHOW_TOOL_CALLS_KEY, value); }, onSystemMessages: (value) => { setShowSystemMessages(value); writeStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, value); }, onClose: () => { setSheet(null); } }))] }));
|
|
303
351
|
}
|
|
304
352
|
/* ── message rows ─────────────────────────────────────────────────────── */
|
|
305
353
|
/** One rendered message row (user bubble or assistant bubble with folds). */
|
|
306
|
-
function MessageRow({ message }) {
|
|
307
|
-
|
|
354
|
+
function MessageRow({ message, showToolCalls, showSystemMessages }) {
|
|
355
|
+
// Injected user messages (sourceKind defined and not 'user') hide behind
|
|
356
|
+
// the system-message toggle. Assistant messages are never hidden.
|
|
357
|
+
if (message.kind === 'user'
|
|
358
|
+
&& message.sourceKind !== undefined
|
|
359
|
+
&& message.sourceKind !== 'user'
|
|
360
|
+
&& !showSystemMessages) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
return (_jsxs("div", { className: `chat-msg chat-msg-${message.kind}${message.pending === true ? ' chat-msg-pending' : ''}${message.failed === true ? ' chat-msg-failed' : ''}`, children: [message.kind === 'assistant' && message.reasoning !== undefined && message.reasoning !== '' && (_jsx(ReasoningDisclosure, { text: message.reasoning, pending: message.pending === true })), showToolCalls && message.kind === 'assistant' && message.tools !== undefined && message.tools.length > 0 && (_jsx(ToolDisclosure, { tools: message.tools })), message.kind === 'assistant'
|
|
364
|
+
? _jsx(MarkdownText, { text: message.text })
|
|
365
|
+
: _jsx(CollapsibleText, { text: message.text }), message.failed === true && _jsx("span", { className: "chat-msg-failtag", children: "\u672C\u6B21\u56DE\u590D\u5931\u8D25" }), _jsx("span", { className: "chat-msg-time", children: formatTime(message.time) })] }));
|
|
308
366
|
}
|
|
309
367
|
/** Collapsed-by-default reasoning disclosure (web-UI Think-row parity). */
|
|
310
368
|
function ReasoningDisclosure({ text, pending }) {
|
|
@@ -318,6 +376,20 @@ function ToolDisclosure({ tools }) {
|
|
|
318
376
|
const names = [...new Set(tools.map(tool => tool.name))].join(' / ');
|
|
319
377
|
return (_jsxs("div", { className: `chat-disclosure chat-tools${open ? ' chat-disclosure-open' : ''}`, children: [_jsxs("button", { type: "button", className: "chat-disclosure-head", "aria-expanded": open, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: "chat-disclosure-caret", "aria-hidden": true, children: "\u203A" }), _jsx("span", { className: "chat-disclosure-label", children: "\u5DE5\u5177" }), !open && _jsx("span", { className: "chat-disclosure-summary", children: names }), _jsxs("span", { className: "chat-disclosure-count", children: [tools.length, " \u6B21"] })] }), open && (_jsx("div", { className: "chat-disclosure-body chat-tools-body", children: tools.map((tool, index) => (_jsxs("div", { className: "chat-tool-item", children: [_jsx("span", { className: "chat-tool-name", children: tool.name }), tool.arguments !== undefined && _jsx("pre", { className: "chat-tool-args", children: tool.arguments })] }, `${tool.callId}-${index}`))) }))] }));
|
|
320
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Assistant text rendered as GFM markdown (escape-first, protocol
|
|
381
|
+
* allow-list — see markdown.ts). Long replies collapse by clamping the
|
|
382
|
+
* rendered block height instead of slicing the source, so half-cut code
|
|
383
|
+
* fences or tables never leak malformed markup into the DOM. User
|
|
384
|
+
* messages stay plain text (CollapsibleText).
|
|
385
|
+
*/
|
|
386
|
+
function MarkdownText({ text }) {
|
|
387
|
+
const [open, setOpen] = useState(false);
|
|
388
|
+
const html = useMemo(() => renderMarkdown(text), [text]);
|
|
389
|
+
const long = text.length > LONG_TEXT_LIMIT;
|
|
390
|
+
const collapsed = long && !open;
|
|
391
|
+
return (_jsxs("div", { className: 'chat-msg-text chat-md' + (collapsed ? ' chat-md-collapsed' : ''), children: [_jsx("div", { className: "chat-md-body", dangerouslySetInnerHTML: { __html: html } }), long && (_jsx("button", { type: "button", className: "chat-msg-toggle", onClick: () => { setOpen(value => !value); }, children: open ? '收起' : '展开全文(' + text.length + ' 字)' }))] }));
|
|
392
|
+
}
|
|
321
393
|
/** Long assistant text collapses behind an explicit expand toggle. */
|
|
322
394
|
function CollapsibleText({ text }) {
|
|
323
395
|
const [open, setOpen] = useState(false);
|
|
@@ -448,3 +520,7 @@ function PermissionSheet({ sessionId, value, onChanged, onClose }) {
|
|
|
448
520
|
return (_jsxs("button", { type: "button", className: `sheet-option${isSelected ? ' sheet-option-selected' : ''}`, disabled: busy, onClick: () => { choose(option.value); }, children: [_jsxs("span", { className: "sheet-option-copy", children: [_jsx("span", { className: "sheet-option-title", children: option.name }), option.description !== undefined && _jsx("span", { className: "sheet-option-desc", children: option.description })] }), isSelected && _jsx("span", { className: "sheet-option-check", "aria-hidden": true, children: "\u221A" })] }, option.value));
|
|
449
521
|
})] }));
|
|
450
522
|
}
|
|
523
|
+
/** The display-options sheet: tool calls and injected system messages toggles. */
|
|
524
|
+
function DisplaySheet({ showToolCalls, showSystemMessages, onToolCalls, onSystemMessages, onClose }) {
|
|
525
|
+
return (_jsx(Sheet, { title: "\u663E\u793A", onClose: onClose, children: _jsxs("div", { role: "group", "aria-label": "\u663E\u793A\u9009\u9879", children: [_jsxs("div", { className: "sheet-toggle-row", children: [_jsxs("div", { className: "sheet-toggle-copy", children: [_jsx("span", { className: "sheet-toggle-title", children: "\u5DE5\u5177\u8C03\u7528" }), _jsx("span", { className: "sheet-toggle-desc", children: "\u663E\u793A\u52A9\u624B\u4F7F\u7528\u7684\u5DE5\u5177\u8C03\u7528" })] }), _jsx("button", { type: "button", role: "switch", "aria-label": "\u5DE5\u5177\u8C03\u7528", "aria-checked": showToolCalls, className: `sheet-toggle-switch${showToolCalls ? ' sheet-toggle-switch-on' : ''}`, onClick: () => { onToolCalls(!showToolCalls); }, children: _jsx("span", { className: "sheet-toggle-switch-knob", "aria-hidden": true }) })] }), _jsxs("div", { className: "sheet-toggle-row", children: [_jsxs("div", { className: "sheet-toggle-copy", children: [_jsx("span", { className: "sheet-toggle-title", children: "\u7CFB\u7EDF\u63D0\u793A\u8BCD" }), _jsx("span", { className: "sheet-toggle-desc", children: "\u663E\u793A\u6CE8\u5165\u5230\u5BF9\u8BDD\u4E2D\u7684\u7CFB\u7EDF\u6D88\u606F" })] }), _jsx("button", { type: "button", role: "switch", "aria-label": "\u7CFB\u7EDF\u63D0\u793A\u8BCD", "aria-checked": showSystemMessages, className: `sheet-toggle-switch${showSystemMessages ? ' sheet-toggle-switch-on' : ''}`, onClick: () => { onSystemMessages(!showSystemMessages); }, children: _jsx("span", { className: "sheet-toggle-switch-knob", "aria-hidden": true }) })] })] }) }));
|
|
526
|
+
}
|