@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
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
* permission pickers, both as bottom sheets.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
15
15
|
import type { MuxFrame } from '@deepseek-ai/dsh-host-apiproxy/api/events'
|
|
16
16
|
import type { SessionModels } from '@deepseek-ai/dsh-host-apiproxy/api/sessions'
|
|
17
17
|
import { loadHistory, prompt, type SessionView } from './App.tsx'
|
|
18
18
|
import { errorText, formatTime, staleHostHint } from './App.tsx'
|
|
19
19
|
import { fetchMobilePreferences, models, selectModel, sendCommand } from '../api.ts'
|
|
20
20
|
import { foldEvents, type RenderMessage, type ToolCallInfo, type WireEvent } from '../messages.ts'
|
|
21
|
+
import { renderMarkdown } from '../markdown.ts'
|
|
21
22
|
import { MuxClient } from '../mux.ts'
|
|
22
23
|
import { ThemeToggle } from '../theme-toggle.tsx'
|
|
23
24
|
|
|
@@ -36,6 +37,31 @@ export interface ChatViewProps {
|
|
|
36
37
|
*/
|
|
37
38
|
export const MAX_TAIL_BUFFER_EVENTS = 500
|
|
38
39
|
|
|
40
|
+
/** localStorage key for the tool-call display toggle (persisted on the /m origin). */
|
|
41
|
+
const SHOW_TOOL_CALLS_KEY = 'dsh.mobile.showToolCalls'
|
|
42
|
+
/** localStorage key for the injected-system-message display toggle. */
|
|
43
|
+
const SHOW_SYSTEM_MESSAGES_KEY = 'dsh.mobile.showSystemMessages'
|
|
44
|
+
|
|
45
|
+
/** Read a boolean from localStorage defensively; falls back to the default. */
|
|
46
|
+
function readStoredBoolean(key: string, fallback: boolean): boolean {
|
|
47
|
+
try {
|
|
48
|
+
const raw = localStorage.getItem(key)
|
|
49
|
+
if (raw === null) return fallback
|
|
50
|
+
return raw === '1' || raw.toLowerCase() === 'true'
|
|
51
|
+
} catch {
|
|
52
|
+
return fallback
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Persist a boolean toggle; storage failures are ignored (feature stays non-persistent). */
|
|
57
|
+
function writeStoredBoolean(key: string, value: boolean): void {
|
|
58
|
+
try {
|
|
59
|
+
localStorage.setItem(key, value ? '1' : '0')
|
|
60
|
+
} catch {
|
|
61
|
+
/* quota / privacy mode: non-persistent is acceptable */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
/** Extract the raw event from one history entry (the fold consumes events only). */
|
|
40
66
|
function eventOf(entry: { event: WireEvent }): WireEvent {
|
|
41
67
|
return entry.event
|
|
@@ -126,7 +152,11 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
126
152
|
/** The current model selection for the toolbar chip (best-effort label). */
|
|
127
153
|
const [currentModel, setCurrentModel] = useState<{ provider: string; model: string; reasoningEffort?: string } | undefined>(undefined)
|
|
128
154
|
/** Which bottom sheet is open. */
|
|
129
|
-
const [sheet, setSheet] = useState<'model' | 'permission' | null>(null)
|
|
155
|
+
const [sheet, setSheet] = useState<'model' | 'permission' | 'display' | null>(null)
|
|
156
|
+
/** Show tool-call disclosures (default on, persisted on the /m origin). */
|
|
157
|
+
const [showToolCalls, setShowToolCalls] = useState<boolean>(() => readStoredBoolean(SHOW_TOOL_CALLS_KEY, true))
|
|
158
|
+
/** Show injected system messages (default off, persisted on the /m origin). */
|
|
159
|
+
const [showSystemMessages, setShowSystemMessages] = useState<boolean>(() => readStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, false))
|
|
130
160
|
/**
|
|
131
161
|
* Composer preference from the plugin's host settings (default true keeps
|
|
132
162
|
* the legacy Enter-to-send behavior until the preference loads).
|
|
@@ -334,6 +364,23 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
334
364
|
: permissions.options.find(option => option.value === permissions.currentValue)?.name
|
|
335
365
|
?? displayName(permissions.currentValue)
|
|
336
366
|
|
|
367
|
+
// Context usage chip: the most recent assistant message carrying both usage and
|
|
368
|
+
// a positive context window drives the percentage. Scanned from the end so a
|
|
369
|
+
// newer answer (whose usage may be the last one reported) takes precedence.
|
|
370
|
+
const contextUsage = useMemo(() => {
|
|
371
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
372
|
+
const message = messages[index]
|
|
373
|
+
const usage = message.usage
|
|
374
|
+
if (message.kind !== 'assistant' || usage === undefined) continue
|
|
375
|
+
const window = message.contextWindow
|
|
376
|
+
if (window === undefined || window <= 0) continue
|
|
377
|
+
const tokens = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0)
|
|
378
|
+
const pct = Math.round(tokens / window * 100)
|
|
379
|
+
return { pct }
|
|
380
|
+
}
|
|
381
|
+
return undefined
|
|
382
|
+
}, [messages])
|
|
383
|
+
|
|
337
384
|
return (
|
|
338
385
|
<div className="chat">
|
|
339
386
|
<header className="mobile-header">
|
|
@@ -348,7 +395,14 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
348
395
|
{loading ? '加载中…' : '加载更早的消息'}
|
|
349
396
|
</button>
|
|
350
397
|
)}
|
|
351
|
-
{messages.map(message =>
|
|
398
|
+
{messages.map(message => (
|
|
399
|
+
<MessageRow
|
|
400
|
+
key={message.id}
|
|
401
|
+
message={message}
|
|
402
|
+
showToolCalls={showToolCalls}
|
|
403
|
+
showSystemMessages={showSystemMessages}
|
|
404
|
+
/>
|
|
405
|
+
))}
|
|
352
406
|
{loading && messages.length === 0 && <p className="chat-typing">加载中…</p>}
|
|
353
407
|
{!loading && messages.length === 0 && <p className="chat-typing">还没有消息,发一句话开始吧</p>}
|
|
354
408
|
</div>
|
|
@@ -358,6 +412,10 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
358
412
|
<span className="chat-chip-value">{modelLabel}</span>
|
|
359
413
|
<span className="chat-chip-chevron" aria-hidden>›</span>
|
|
360
414
|
</button>
|
|
415
|
+
<button type="button" className="chat-chip" onClick={() => { setSheet('display') }} aria-haspopup="dialog">
|
|
416
|
+
<span className="chat-chip-label">显示</span>
|
|
417
|
+
<span className="chat-chip-chevron" aria-hidden>›</span>
|
|
418
|
+
</button>
|
|
361
419
|
{permissionLabel !== undefined && (
|
|
362
420
|
<button type="button" className="chat-chip" onClick={() => { setSheet('permission') }} aria-haspopup="dialog">
|
|
363
421
|
<span className="chat-chip-label">权限</span>
|
|
@@ -365,6 +423,11 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
365
423
|
<span className="chat-chip-chevron" aria-hidden>›</span>
|
|
366
424
|
</button>
|
|
367
425
|
)}
|
|
426
|
+
{contextUsage !== undefined && (
|
|
427
|
+
<div className={"chat-context" + (contextUsage.pct >= 80 ? " chat-context-warn" : "")}>
|
|
428
|
+
上下文 {contextUsage.pct}%
|
|
429
|
+
</div>
|
|
430
|
+
)}
|
|
368
431
|
</div>
|
|
369
432
|
<div className="chat-inputbar">
|
|
370
433
|
<textarea
|
|
@@ -403,6 +466,15 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
403
466
|
onClose={() => { setSheet(null) }}
|
|
404
467
|
/>
|
|
405
468
|
)}
|
|
469
|
+
{sheet === 'display' && (
|
|
470
|
+
<DisplaySheet
|
|
471
|
+
showToolCalls={showToolCalls}
|
|
472
|
+
showSystemMessages={showSystemMessages}
|
|
473
|
+
onToolCalls={(value) => { setShowToolCalls(value); writeStoredBoolean(SHOW_TOOL_CALLS_KEY, value) }}
|
|
474
|
+
onSystemMessages={(value) => { setShowSystemMessages(value); writeStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, value) }}
|
|
475
|
+
onClose={() => { setSheet(null) }}
|
|
476
|
+
/>
|
|
477
|
+
)}
|
|
406
478
|
</div>
|
|
407
479
|
)
|
|
408
480
|
}
|
|
@@ -410,16 +482,30 @@ export function ChatView({ session, mux, onBack }: ChatViewProps) {
|
|
|
410
482
|
/* ── message rows ─────────────────────────────────────────────────────── */
|
|
411
483
|
|
|
412
484
|
/** One rendered message row (user bubble or assistant bubble with folds). */
|
|
413
|
-
function MessageRow({ message
|
|
485
|
+
function MessageRow({ message, showToolCalls, showSystemMessages }: {
|
|
486
|
+
message: RenderMessage
|
|
487
|
+
showToolCalls: boolean
|
|
488
|
+
showSystemMessages: boolean
|
|
489
|
+
}) {
|
|
490
|
+
// Injected user messages (sourceKind defined and not 'user') hide behind
|
|
491
|
+
// the system-message toggle. Assistant messages are never hidden.
|
|
492
|
+
if (message.kind === 'user'
|
|
493
|
+
&& message.sourceKind !== undefined
|
|
494
|
+
&& message.sourceKind !== 'user'
|
|
495
|
+
&& !showSystemMessages) {
|
|
496
|
+
return null
|
|
497
|
+
}
|
|
414
498
|
return (
|
|
415
499
|
<div className={`chat-msg chat-msg-${message.kind}${message.pending === true ? ' chat-msg-pending' : ''}${message.failed === true ? ' chat-msg-failed' : ''}`}>
|
|
416
500
|
{message.kind === 'assistant' && message.reasoning !== undefined && message.reasoning !== '' && (
|
|
417
501
|
<ReasoningDisclosure text={message.reasoning} pending={message.pending === true} />
|
|
418
502
|
)}
|
|
419
|
-
{message.kind === 'assistant' && message.tools !== undefined && message.tools.length > 0 && (
|
|
503
|
+
{showToolCalls && message.kind === 'assistant' && message.tools !== undefined && message.tools.length > 0 && (
|
|
420
504
|
<ToolDisclosure tools={message.tools} />
|
|
421
505
|
)}
|
|
422
|
-
|
|
506
|
+
{message.kind === 'assistant'
|
|
507
|
+
? <MarkdownText text={message.text} />
|
|
508
|
+
: <CollapsibleText text={message.text} />}
|
|
423
509
|
{message.failed === true && <span className="chat-msg-failtag">本次回复失败</span>}
|
|
424
510
|
<span className="chat-msg-time">{formatTime(message.time)}</span>
|
|
425
511
|
</div>
|
|
@@ -478,6 +564,30 @@ function ToolDisclosure({ tools }: { tools: ToolCallInfo[] }) {
|
|
|
478
564
|
)
|
|
479
565
|
}
|
|
480
566
|
|
|
567
|
+
/**
|
|
568
|
+
* Assistant text rendered as GFM markdown (escape-first, protocol
|
|
569
|
+
* allow-list — see markdown.ts). Long replies collapse by clamping the
|
|
570
|
+
* rendered block height instead of slicing the source, so half-cut code
|
|
571
|
+
* fences or tables never leak malformed markup into the DOM. User
|
|
572
|
+
* messages stay plain text (CollapsibleText).
|
|
573
|
+
*/
|
|
574
|
+
function MarkdownText({ text }: { text: string }) {
|
|
575
|
+
const [open, setOpen] = useState(false)
|
|
576
|
+
const html = useMemo(() => renderMarkdown(text), [text])
|
|
577
|
+
const long = text.length > LONG_TEXT_LIMIT
|
|
578
|
+
const collapsed = long && !open
|
|
579
|
+
return (
|
|
580
|
+
<div className={'chat-msg-text chat-md' + (collapsed ? ' chat-md-collapsed' : '')}>
|
|
581
|
+
<div className="chat-md-body" dangerouslySetInnerHTML={{ __html: html }} />
|
|
582
|
+
{long && (
|
|
583
|
+
<button type="button" className="chat-msg-toggle" onClick={() => { setOpen(value => !value) }}>
|
|
584
|
+
{open ? '收起' : '展开全文(' + text.length + ' 字)'}
|
|
585
|
+
</button>
|
|
586
|
+
)}
|
|
587
|
+
</div>
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
481
591
|
/** Long assistant text collapses behind an explicit expand toggle. */
|
|
482
592
|
function CollapsibleText({ text }: { text: string }) {
|
|
483
593
|
const [open, setOpen] = useState(false)
|
|
@@ -756,3 +866,51 @@ function PermissionSheet({ sessionId, value, onChanged, onClose }: {
|
|
|
756
866
|
</Sheet>
|
|
757
867
|
)
|
|
758
868
|
}
|
|
869
|
+
|
|
870
|
+
/** The display-options sheet: tool calls and injected system messages toggles. */
|
|
871
|
+
function DisplaySheet({ showToolCalls, showSystemMessages, onToolCalls, onSystemMessages, onClose }: {
|
|
872
|
+
showToolCalls: boolean
|
|
873
|
+
showSystemMessages: boolean
|
|
874
|
+
onToolCalls(value: boolean): void
|
|
875
|
+
onSystemMessages(value: boolean): void
|
|
876
|
+
onClose(): void
|
|
877
|
+
}) {
|
|
878
|
+
return (
|
|
879
|
+
<Sheet title="显示" onClose={onClose}>
|
|
880
|
+
<div role="group" aria-label="显示选项">
|
|
881
|
+
<div className="sheet-toggle-row">
|
|
882
|
+
<div className="sheet-toggle-copy">
|
|
883
|
+
<span className="sheet-toggle-title">工具调用</span>
|
|
884
|
+
<span className="sheet-toggle-desc">显示助手使用的工具调用</span>
|
|
885
|
+
</div>
|
|
886
|
+
<button
|
|
887
|
+
type="button"
|
|
888
|
+
role="switch"
|
|
889
|
+
aria-label="工具调用"
|
|
890
|
+
aria-checked={showToolCalls}
|
|
891
|
+
className={`sheet-toggle-switch${showToolCalls ? ' sheet-toggle-switch-on' : ''}`}
|
|
892
|
+
onClick={() => { onToolCalls(!showToolCalls) }}
|
|
893
|
+
>
|
|
894
|
+
<span className="sheet-toggle-switch-knob" aria-hidden />
|
|
895
|
+
</button>
|
|
896
|
+
</div>
|
|
897
|
+
<div className="sheet-toggle-row">
|
|
898
|
+
<div className="sheet-toggle-copy">
|
|
899
|
+
<span className="sheet-toggle-title">系统提示词</span>
|
|
900
|
+
<span className="sheet-toggle-desc">显示注入到对话中的系统消息</span>
|
|
901
|
+
</div>
|
|
902
|
+
<button
|
|
903
|
+
type="button"
|
|
904
|
+
role="switch"
|
|
905
|
+
aria-label="系统提示词"
|
|
906
|
+
aria-checked={showSystemMessages}
|
|
907
|
+
className={`sheet-toggle-switch${showSystemMessages ? ' sheet-toggle-switch-on' : ''}`}
|
|
908
|
+
onClick={() => { onSystemMessages(!showSystemMessages) }}
|
|
909
|
+
>
|
|
910
|
+
<span className="sheet-toggle-switch-knob" aria-hidden />
|
|
911
|
+
</button>
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
</Sheet>
|
|
915
|
+
)
|
|
916
|
+
}
|