@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.10

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.
Files changed (238) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +77 -468
  3. package/README.zh.md +83 -113
  4. package/lib/client.js +993 -203
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1136 -1466
  7. package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
  8. package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
  9. package/lib/types/client/FooterRemoteEntry.js +1 -1
  10. package/lib/types/client/RemoteEntry.d.ts +7 -4
  11. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  12. package/lib/types/client/RemoteEntry.js +7 -10
  13. package/lib/types/client/RemotePanel.d.ts +2 -2
  14. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  15. package/lib/types/client/RemotePanel.js +2 -2
  16. package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
  17. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  18. package/lib/types/client/RemoteSettingsCard.js +56 -3
  19. package/lib/types/client/deep-link.d.ts +10 -13
  20. package/lib/types/client/deep-link.d.ts.map +1 -1
  21. package/lib/types/client/deep-link.js +14 -74
  22. package/lib/types/client/index.d.ts +2 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +79 -39
  25. package/lib/types/client/locales.d.ts +32 -12
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +42 -22
  28. package/lib/types/client/mobile-adapt.d.ts +49 -0
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -0
  30. package/lib/types/client/mobile-adapt.js +1002 -0
  31. package/lib/types/client/pair-api.d.ts +24 -6
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +9 -14
  34. package/lib/types/client/remote-channel.d.ts +3 -0
  35. package/lib/types/client/remote-channel.d.ts.map +1 -1
  36. package/lib/types/client/remote-channel.js +35 -3
  37. package/lib/types/client/settings-form.d.ts +35 -43
  38. package/lib/types/client/settings-form.d.ts.map +1 -1
  39. package/lib/types/client/settings-form.js +60 -53
  40. package/lib/types/firewall.d.ts +75 -0
  41. package/lib/types/firewall.d.ts.map +1 -0
  42. package/lib/types/firewall.js +184 -0
  43. package/lib/types/gate.d.ts +11 -5
  44. package/lib/types/gate.d.ts.map +1 -1
  45. package/lib/types/gate.js +11 -5
  46. package/lib/types/index.d.ts +39 -10
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +206 -58
  49. package/lib/types/inner-auth.d.ts +32 -0
  50. package/lib/types/inner-auth.d.ts.map +1 -0
  51. package/lib/types/inner-auth.js +72 -0
  52. package/lib/types/lan-bind-plan.d.ts +55 -0
  53. package/lib/types/lan-bind-plan.d.ts.map +1 -0
  54. package/lib/types/lan-bind-plan.js +60 -0
  55. package/lib/types/lan-bind.d.ts +69 -0
  56. package/lib/types/lan-bind.d.ts.map +1 -0
  57. package/lib/types/lan-bind.js +128 -0
  58. package/lib/types/loopback-proxy.d.ts +9 -2
  59. package/lib/types/loopback-proxy.d.ts.map +1 -1
  60. package/lib/types/loopback-proxy.js +24 -2
  61. package/lib/types/pairing.d.ts +15 -3
  62. package/lib/types/pairing.d.ts.map +1 -1
  63. package/lib/types/pairing.js +20 -6
  64. package/lib/types/remote-api.d.ts +30 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +55 -42
  67. package/lib/types/remote-channel-boot.d.ts +21 -2
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +65 -3
  70. package/lib/types/remote-channel-rules.d.ts +8 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +9 -2
  73. package/lib/types/remote-methods.d.ts +45 -11
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +56 -27
  76. package/lib/types/remote-presence-pet.d.ts +51 -0
  77. package/lib/types/remote-presence-pet.d.ts.map +1 -0
  78. package/lib/types/remote-presence-pet.js +101 -0
  79. package/lib/types/routes.d.ts +76 -4
  80. package/lib/types/routes.d.ts.map +1 -1
  81. package/lib/types/routes.js +336 -19
  82. package/lib/types/tunnel.d.ts.map +1 -1
  83. package/lib/types/tunnel.js +7 -1
  84. package/package.json +20 -18
  85. package/src/client/FooterRemoteEntry.tsx +9 -10
  86. package/src/client/RemoteEntry.tsx +13 -14
  87. package/src/client/RemotePanel.tsx +5 -16
  88. package/src/client/RemoteSettingsCard.tsx +72 -13
  89. package/src/client/deep-link.ts +14 -75
  90. package/src/client/index.ts +82 -39
  91. package/src/client/locales.ts +42 -22
  92. package/src/client/mobile-adapt.ts +954 -0
  93. package/src/client/pair-api.ts +26 -15
  94. package/src/client/remote-channel.ts +31 -3
  95. package/src/client/settings-form.ts +78 -92
  96. package/src/dsh-home.ts +1 -1
  97. package/src/firewall.ts +230 -0
  98. package/src/gate.ts +11 -5
  99. package/src/index.ts +235 -65
  100. package/src/inner-auth.ts +85 -0
  101. package/src/lan-bind-plan.ts +81 -0
  102. package/src/lan-bind.ts +145 -0
  103. package/src/loopback-proxy.ts +34 -0
  104. package/src/pairing.persist.test.ts +1 -1
  105. package/src/pairing.ts +20 -6
  106. package/src/remote-api.ts +67 -45
  107. package/src/remote-channel-boot.ts +70 -3
  108. package/src/remote-channel-rules.ts +17 -2
  109. package/src/remote-methods.ts +58 -27
  110. package/src/remote-presence-pet.ts +129 -0
  111. package/src/routes.ts +358 -19
  112. package/src/tunnel.ts +7 -1
  113. package/assets/apple-touch-icon.png +0 -0
  114. package/assets/mobile-icon-192.png +0 -0
  115. package/assets/mobile-icon-512.png +0 -0
  116. package/assets/mobile-service-worker.js +0 -63
  117. package/lib/mobile.js +0 -17658
  118. package/lib/mobile.js.map +0 -1
  119. package/lib/types/mobile/PairRequiredView.d.ts +0 -7
  120. package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
  121. package/lib/types/mobile/PairRequiredView.js +0 -28
  122. package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
  123. package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
  124. package/lib/types/mobile/PairRequiredView.test.js +0 -24
  125. package/lib/types/mobile/api.d.ts +0 -145
  126. package/lib/types/mobile/api.d.ts.map +0 -1
  127. package/lib/types/mobile/api.js +0 -103
  128. package/lib/types/mobile/index.d.ts +0 -10
  129. package/lib/types/mobile/index.d.ts.map +0 -1
  130. package/lib/types/mobile/index.js +0 -38
  131. package/lib/types/mobile/markdown.d.ts +0 -34
  132. package/lib/types/mobile/markdown.d.ts.map +0 -1
  133. package/lib/types/mobile/markdown.js +0 -285
  134. package/lib/types/mobile/markdown.test.d.ts +0 -2
  135. package/lib/types/mobile/markdown.test.d.ts.map +0 -1
  136. package/lib/types/mobile/markdown.test.js +0 -79
  137. package/lib/types/mobile/messages.d.ts +0 -149
  138. package/lib/types/mobile/messages.d.ts.map +0 -1
  139. package/lib/types/mobile/messages.js +0 -561
  140. package/lib/types/mobile/messages.test.d.ts +0 -2
  141. package/lib/types/mobile/messages.test.d.ts.map +0 -1
  142. package/lib/types/mobile/messages.test.js +0 -356
  143. package/lib/types/mobile/mobile-styles.d.ts +0 -6
  144. package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
  145. package/lib/types/mobile/mobile-styles.js +0 -1438
  146. package/lib/types/mobile/mobile-theme.d.ts +0 -22
  147. package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
  148. package/lib/types/mobile/mobile-theme.js +0 -70
  149. package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
  150. package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
  151. package/lib/types/mobile/mobile-theme.test.js +0 -71
  152. package/lib/types/mobile/mux.d.ts +0 -128
  153. package/lib/types/mobile/mux.d.ts.map +0 -1
  154. package/lib/types/mobile/mux.js +0 -305
  155. package/lib/types/mobile/mux.test.d.ts +0 -2
  156. package/lib/types/mobile/mux.test.d.ts.map +0 -1
  157. package/lib/types/mobile/mux.test.js +0 -267
  158. package/lib/types/mobile/pairing.d.ts +0 -31
  159. package/lib/types/mobile/pairing.d.ts.map +0 -1
  160. package/lib/types/mobile/pairing.js +0 -62
  161. package/lib/types/mobile/rpc.d.ts +0 -38
  162. package/lib/types/mobile/rpc.d.ts.map +0 -1
  163. package/lib/types/mobile/rpc.js +0 -83
  164. package/lib/types/mobile/theme-toggle.d.ts +0 -12
  165. package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
  166. package/lib/types/mobile/theme-toggle.js +0 -26
  167. package/lib/types/mobile/views/App.d.ts +0 -57
  168. package/lib/types/mobile/views/App.d.ts.map +0 -1
  169. package/lib/types/mobile/views/App.js +0 -150
  170. package/lib/types/mobile/views/App.test.d.ts +0 -2
  171. package/lib/types/mobile/views/App.test.d.ts.map +0 -1
  172. package/lib/types/mobile/views/App.test.js +0 -52
  173. package/lib/types/mobile/views/ChatView.d.ts +0 -63
  174. package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
  175. package/lib/types/mobile/views/ChatView.js +0 -821
  176. package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
  177. package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
  178. package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
  179. package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
  180. package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
  181. package/lib/types/mobile/views/ChatView.test.js +0 -718
  182. package/lib/types/mobile/views/SessionListView.d.ts +0 -27
  183. package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
  184. package/lib/types/mobile/views/SessionListView.js +0 -139
  185. package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
  186. package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
  187. package/lib/types/mobile/views/SessionListView.test.js +0 -167
  188. package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
  189. package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
  190. package/lib/types/mobile/views/WorkspaceView.js +0 -93
  191. package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
  192. package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
  193. package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
  194. package/lib/types/mobile-api.d.ts +0 -65
  195. package/lib/types/mobile-api.d.ts.map +0 -1
  196. package/lib/types/mobile-api.js +0 -404
  197. package/lib/types/mobile-pending.d.ts +0 -38
  198. package/lib/types/mobile-pending.d.ts.map +0 -1
  199. package/lib/types/mobile-pending.js +0 -59
  200. package/lib/types/mobile-pwa.d.ts +0 -6
  201. package/lib/types/mobile-pwa.d.ts.map +0 -1
  202. package/lib/types/mobile-pwa.js +0 -17
  203. package/lib/types/mobile-routes.d.ts +0 -13
  204. package/lib/types/mobile-routes.d.ts.map +0 -1
  205. package/lib/types/mobile-routes.js +0 -217
  206. package/lib/types/paired-model-catalog.d.ts +0 -17
  207. package/lib/types/paired-model-catalog.d.ts.map +0 -1
  208. package/lib/types/paired-model-catalog.js +0 -311
  209. package/src/mobile/PairRequiredView.test.tsx +0 -28
  210. package/src/mobile/PairRequiredView.tsx +0 -58
  211. package/src/mobile/api.ts +0 -229
  212. package/src/mobile/index.tsx +0 -41
  213. package/src/mobile/markdown.test.ts +0 -91
  214. package/src/mobile/markdown.ts +0 -291
  215. package/src/mobile/messages.test.ts +0 -402
  216. package/src/mobile/messages.ts +0 -685
  217. package/src/mobile/mobile-styles.ts +0 -1439
  218. package/src/mobile/mobile-theme.test.ts +0 -84
  219. package/src/mobile/mobile-theme.ts +0 -76
  220. package/src/mobile/mux.test.ts +0 -314
  221. package/src/mobile/mux.ts +0 -336
  222. package/src/mobile/pairing.ts +0 -77
  223. package/src/mobile/rpc.ts +0 -90
  224. package/src/mobile/theme-toggle.tsx +0 -47
  225. package/src/mobile/views/App.test.tsx +0 -62
  226. package/src/mobile/views/App.tsx +0 -213
  227. package/src/mobile/views/ChatView.stream.test.tsx +0 -268
  228. package/src/mobile/views/ChatView.test.tsx +0 -816
  229. package/src/mobile/views/ChatView.tsx +0 -1342
  230. package/src/mobile/views/SessionListView.test.tsx +0 -207
  231. package/src/mobile/views/SessionListView.tsx +0 -244
  232. package/src/mobile/views/WorkspaceView.test.tsx +0 -227
  233. package/src/mobile/views/WorkspaceView.tsx +0 -245
  234. package/src/mobile-api.ts +0 -418
  235. package/src/mobile-pending.ts +0 -88
  236. package/src/mobile-pwa.ts +0 -19
  237. package/src/mobile-routes.ts +0 -231
  238. package/src/paired-model-catalog.ts +0 -272
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=ChatView.stream.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChatView.stream.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/ChatView.stream.test.tsx"],"names":[],"mappings":""}
@@ -1,236 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- // @vitest-environment jsdom
3
- /**
4
- * ChatView streaming markdown throttle: a pending assistant message must not
5
- * re-parse its whole accumulated text on every chunk. Streaming text
6
- * re-parses at most once per STREAM_RENDER_INTERVAL_MS, shows the last
7
- * parsed result in between, and renders the exact final HTML (byte-identical
8
- * to the plain renderMarkdown output) the moment the turn closes.
9
- */
10
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
- import { act, cleanup, render, screen } from '@testing-library/react';
12
- import { ChatView, STREAM_RENDER_INTERVAL_MS, LONG_TEXT_LIMIT, FOLD_FLUSH_MS } from "./ChatView.js";
13
- import { renderMarkdown } from "../markdown.js";
14
- // The api module is fully mocked; App.tsx's history wrapper is overridden to
15
- // feed a fixed empty history page, its pure helpers stay real.
16
- vi.mock('../api.ts', () => ({
17
- fetchMobilePreferences: vi.fn(),
18
- models: vi.fn(),
19
- selectModel: vi.fn(),
20
- sendCommand: vi.fn(),
21
- cancelSession: vi.fn(),
22
- fetchPending: vi.fn(),
23
- }));
24
- vi.mock('./App.tsx', async (importOriginal) => {
25
- const actual = await importOriginal();
26
- return {
27
- ...actual,
28
- loadHistory: vi.fn(),
29
- prompt: vi.fn(async () => { }),
30
- };
31
- });
32
- // Wrap the real renderer with a call counter: the throttle must cut the
33
- // number of full-text parses during a chunk stream (each parse is O(n)).
34
- vi.mock('../markdown.ts', async (importOriginal) => {
35
- const actual = await importOriginal();
36
- return { ...actual, renderMarkdown: vi.fn(actual.renderMarkdown) };
37
- });
38
- import { fetchMobilePreferences, models } from "../api.js";
39
- import { loadHistory } from "./App.js";
40
- const loadHistoryMock = vi.mocked(loadHistory);
41
- const fetchMobilePreferencesMock = vi.mocked(fetchMobilePreferences);
42
- const modelsMock = vi.mocked(models);
43
- const renderMarkdownMock = vi.mocked(renderMarkdown);
44
- const session = {
45
- sessionId: 's-1',
46
- title: '测试会话',
47
- updatedAt: 1_700_000_000_000,
48
- running: false,
49
- blank: false,
50
- };
51
- /** Minimal mux stand-in: captures the ChatView's frame listener for hand-off. */
52
- class FakeMux {
53
- listeners = new Set();
54
- onFrame(listener) {
55
- this.listeners.add(listener);
56
- return () => { this.listeners.delete(listener); };
57
- }
58
- emit(frame) {
59
- for (const listener of this.listeners)
60
- listener(frame);
61
- }
62
- }
63
- /** One history entry wrapping a WireEvent (host history-page shape). */
64
- function makeEntry(type, data, seq) {
65
- return { event: { type, seq, time: seq * 1_000, data } };
66
- }
67
- /** Build an empty history page (the live stream supplies all content). */
68
- function historyPage() {
69
- return { events: [], hasMore: false };
70
- }
71
- /**
72
- * Emit one live text-delta chunk bound to the stable message id 'a-1' (the
73
- * mobile wire shape). A stable id keeps the message row mounted across the
74
- * fold, so the tests observe the throttle effect path only.
75
- */
76
- function chunk(mux, text, seq) {
77
- mux.emit({
78
- type: 'session/event',
79
- sessionId: 's-1',
80
- event: makeEntry('assistant/chunk', { messageId: 'a-1', turn: 0, step: 0, text }, seq).event,
81
- });
82
- }
83
- /** Emit the final assistant message (authoritative text, closes the turn). */
84
- function finalMessage(mux, text, seq) {
85
- mux.emit({
86
- type: 'session/event',
87
- sessionId: 's-1',
88
- event: makeEntry('assistant/message', {
89
- turn: 0,
90
- step: 0,
91
- message: { id: 'a-1', role: 'assistant', content: [{ type: 'text', text }] },
92
- }, seq).event,
93
- });
94
- }
95
- /** Round-trip HTML through the DOM so a byte comparison is symmetric. */
96
- function normalize(html) {
97
- const element = document.createElement('div');
98
- element.innerHTML = html;
99
- return element.innerHTML;
100
- }
101
- /** The assistant message's markdown body (dangerouslySetInnerHTML target). */
102
- function body() {
103
- const element = document.querySelector('.chat-msg.chat-msg-assistant .chat-md-body');
104
- if (element === null)
105
- throw new Error('assistant markdown body not found');
106
- return element;
107
- }
108
- beforeEach(() => {
109
- fetchMobilePreferencesMock.mockResolvedValue({ mobileEnterToSend: true });
110
- modelsMock.mockResolvedValue({
111
- current: { provider: 'fx', model: 'fx-1' },
112
- routable: true,
113
- groups: [],
114
- failures: [],
115
- });
116
- loadHistoryMock.mockResolvedValue(historyPage());
117
- vi.useFakeTimers();
118
- });
119
- afterEach(() => {
120
- cleanup();
121
- vi.useRealTimers();
122
- vi.clearAllMocks();
123
- vi.restoreAllMocks();
124
- });
125
- describe('ChatView streaming markdown throttle', () => {
126
- it('re-parses pending text at most once per interval and catches up byte-exactly', async () => {
127
- const mux = new FakeMux();
128
- await act(async () => {
129
- render(_jsx(ChatView, { session: session, mux: mux, onBack: () => { } }));
130
- });
131
- const parts = [
132
- '第一段 **加粗** 文本与 `行内代码`。\n\n',
133
- '第二段 *斜体* 与 <标签> 内容。\n\n',
134
- '第三段 ~~删除线~~ 和 [链接](https://example.com/a)。',
135
- ];
136
- const full = parts.join('');
137
- // The first chunk mounts the pending message on the next fold flush
138
- // (chunk streams fold in batches on the FOLD_FLUSH_MS cadence); the mount
139
- // render parses it.
140
- await act(async () => {
141
- chunk(mux, parts[0] ?? '', 6);
142
- vi.advanceTimersByTime(FOLD_FLUSH_MS + 5);
143
- });
144
- const afterMount = renderMarkdownMock.mock.calls.length;
145
- expect(afterMount).toBeGreaterThanOrEqual(1);
146
- // A burst of further chunks does not re-parse per chunk: at most one
147
- // leading parse may run (if the wall-clock gap to the burst exceeded the
148
- // window), but never one parse per chunk.
149
- await act(async () => {
150
- chunk(mux, parts[1] ?? '', 7);
151
- chunk(mux, parts[2] ?? '', 8);
152
- });
153
- expect(renderMarkdownMock.mock.calls.length - afterMount).toBeLessThan(2);
154
- // During the window the last parsed result stays visible: the newest
155
- // chunk's text has not reached the DOM yet.
156
- expect(body().innerHTML).not.toContain('第三段');
157
- // The trailing render inside the window catches up to the newest text,
158
- // byte-identical to a full renderMarkdown of the accumulated text.
159
- await act(async () => {
160
- vi.advanceTimersByTime(STREAM_RENDER_INTERVAL_MS + 5);
161
- });
162
- expect(normalize(body().innerHTML)).toBe(normalize(renderMarkdown(full)));
163
- expect(renderMarkdownMock.mock.calls.length - afterMount).toBeLessThan(3);
164
- });
165
- it('renders the exact final HTML immediately when the turn closes', async () => {
166
- const mux = new FakeMux();
167
- await act(async () => {
168
- render(_jsx(ChatView, { session: session, mux: mux, onBack: () => { } }));
169
- });
170
- // The final event carries the authoritative text and closes the turn
171
- // (pending -> false) in the same synchronous burst as the chunks.
172
- const finalText = '最终 **整篇** `渲染` 与 [链接](https://example.com/x) 文本。';
173
- await act(async () => {
174
- chunk(mux, '流式前缀', 6);
175
- chunk(mux, '中间过程', 7);
176
- finalMessage(mux, finalText, 8);
177
- });
178
- // No timer advance needed: the closed turn already renders the exact
179
- // final HTML (terminal messages keep the immediate-render behavior).
180
- expect(normalize(body().innerHTML)).toBe(normalize(renderMarkdown(finalText)));
181
- });
182
- it('keeps the streaming render fresh after window edges and skips the identical final parse', async () => {
183
- const mux = new FakeMux();
184
- await act(async () => {
185
- render(_jsx(ChatView, { session: session, mux: mux, onBack: () => { } }));
186
- });
187
- const first = '第一期内容。';
188
- const second = '第二期内容,包含 `code`。';
189
- const full = first + second;
190
- // The chunk and the window edge in one tick: the trailing render fires
191
- // inside the window and shows the current text.
192
- await act(async () => {
193
- chunk(mux, first, 6);
194
- vi.advanceTimersByTime(STREAM_RENDER_INTERVAL_MS + 5);
195
- });
196
- expect(normalize(body().innerHTML)).toBe(normalize(renderMarkdown(first)));
197
- // A later chunk schedules a new trailing render; before it fires the last
198
- // parsed result is still on screen.
199
- await act(async () => { chunk(mux, second, 7); });
200
- expect(body().innerHTML).not.toContain('第二期内容');
201
- await act(async () => {
202
- vi.advanceTimersByTime(STREAM_RENDER_INTERVAL_MS + 5);
203
- });
204
- expect(normalize(body().innerHTML)).toBe(normalize(renderMarkdown(full)));
205
- // Closing the turn with the exact text already rendered must not re-parse
206
- // (the row already holds the byte-identical final HTML).
207
- const beforeFinal = renderMarkdownMock.mock.calls.length;
208
- await act(async () => { finalMessage(mux, full, 9); });
209
- expect(renderMarkdownMock.mock.calls.length - beforeFinal).toBe(0);
210
- expect(normalize(body().innerHTML)).toBe(normalize(renderMarkdown(full)));
211
- });
212
- it('does not collapse long text while streaming (pending) and collapses once turn finishes', async () => {
213
- const mux = new FakeMux();
214
- let container;
215
- await act(async () => {
216
- const rendered = render(_jsx(ChatView, { session: session, mux: mux, onBack: () => { } }));
217
- container = rendered.container;
218
- });
219
- const hugeChunk = 'C'.repeat(LONG_TEXT_LIMIT + 200);
220
- // Stream a chunk exceeding LONG_TEXT_LIMIT
221
- await act(async () => {
222
- chunk(mux, hugeChunk, 6);
223
- vi.advanceTimersByTime(STREAM_RENDER_INTERVAL_MS + 5);
224
- });
225
- // During streaming (pending), message is not collapsed
226
- expect(container.querySelector('.chat-md-collapsed')).toBeNull();
227
- expect(screen.queryByRole('button', { name: /展开全文/ })).toBeNull();
228
- // Turn closes
229
- await act(async () => {
230
- finalMessage(mux, hugeChunk, 7);
231
- });
232
- // Once turn ends (not pending), message is collapsed and has expand button
233
- expect(container.querySelector('.chat-md-collapsed')).not.toBeNull();
234
- expect(screen.getByRole('button', { name: new RegExp(`展开全文(${LONG_TEXT_LIMIT + 200} 字)`) })).toBeTruthy();
235
- });
236
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=ChatView.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ChatView.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/ChatView.test.tsx"],"names":[],"mappings":""}