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

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 +955 -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 +73 -39
  25. package/lib/types/client/locales.d.ts +26 -12
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +36 -22
  28. package/lib/types/client/mobile-adapt.d.ts +40 -0
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -0
  30. package/lib/types/client/mobile-adapt.js +967 -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 +77 -39
  91. package/src/client/locales.ts +36 -22
  92. package/src/client/mobile-adapt.ts +913 -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,27 +0,0 @@
1
- /**
2
- * Sessions level: one workspace's sessions, loaded incrementally — the
3
- * first page fetches session.list with a cursor, scrolling appends further
4
- * pages (never the whole list at once). Rows are filtered to the opened
5
- * workspace by its owned session ids; extra pages are pulled on demand so
6
- * a workspace with many sessions converges without a full transfer.
7
- *
8
- * Creating a session is this level's other action: the workspace's id is
9
- * sent to session.create (the host attaches the new session to it), the
10
- * fresh row is prepended optimistically, and the user lands straight in
11
- * the new chat — the same "new session opens" flow as the desktop UI.
12
- */
13
- import type { WorkspaceView as WorkspaceRow } from '@deepseek-ai/dsh-host-apiproxy/api/workspace';
14
- import { type SessionView } from './App.tsx';
15
- /** Props for the session list. */
16
- export interface SessionListViewProps {
17
- workspace: WorkspaceRow;
18
- onBack(): void;
19
- onPick(session: SessionView): void;
20
- }
21
- /**
22
- * Render one workspace's paged session list.
23
- * @param props - the workspace, back action, and pick action.
24
- * @returns the session list.
25
- */
26
- export declare function SessionListView({ workspace, onBack, onPick }: SessionListViewProps): import("react").JSX.Element;
27
- //# sourceMappingURL=SessionListView.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SessionListView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/SessionListView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,IAAI,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAIjG,OAAO,EAAuD,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAGjG,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,YAAY,CAAA;IACvB,MAAM,IAAI,IAAI,CAAA;IACd,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CACnC;AAUD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,+BA0MlF"}
@@ -1,139 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Sessions level: one workspace's sessions, loaded incrementally — the
4
- * first page fetches session.list with a cursor, scrolling appends further
5
- * pages (never the whole list at once). Rows are filtered to the opened
6
- * workspace by its owned session ids; extra pages are pulled on demand so
7
- * a workspace with many sessions converges without a full transfer.
8
- *
9
- * Creating a session is this level's other action: the workspace's id is
10
- * sent to session.create (the host attaches the new session to it), the
11
- * fresh row is prepended optimistically, and the user lands straight in
12
- * the new chat — the same "new session opens" flow as the desktop UI.
13
- */
14
- import { useCallback, useEffect, useRef, useState } from 'react';
15
- import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
16
- import { errorText, formatTime, staleHostHint, toSessionView } from "./App.js";
17
- import { ThemeToggle } from "../theme-toggle.js";
18
- /** Rows that belong to the opened workspace (its owned session id set). */
19
- function ownedItems(page, workspace) {
20
- const owned = new Set(workspace.sessionIds);
21
- return page
22
- .filter(item => owned.has(item.sessionId))
23
- .map(item => toSessionView(item));
24
- }
25
- /**
26
- * Render one workspace's paged session list.
27
- * @param props - the workspace, back action, and pick action.
28
- * @returns the session list.
29
- */
30
- export function SessionListView({ workspace, onBack, onPick }) {
31
- const [rows, setRows] = useState([]);
32
- const [hasMore, setHasMore] = useState(false);
33
- const [loading, setLoading] = useState(true);
34
- const [error, setError] = useState(undefined);
35
- const [creating, setCreating] = useState(false);
36
- const [createError, setCreateError] = useState(undefined);
37
- const [presets, setPresets] = useState([]);
38
- const [selectedPreset, setSelectedPreset] = useState(undefined);
39
- const [presetsLoading, setPresetsLoading] = useState(true);
40
- const cursorRef = useRef(undefined);
41
- const busyRef = useRef(false);
42
- // The freshest owned-id set: the mount effect re-reads the workspace roster
43
- // so a session created since this row was captured still shows; loadMore
44
- // filters later pages through this ref instead of the stale prop.
45
- const workspaceRef = useRef(workspace);
46
- // First page on mount. The workspace roster is re-read alongside so the
47
- // owned-id set is fresh: a session created (or attached) since this
48
- // workspace row was captured must not vanish from the filter.
49
- useEffect(() => {
50
- let cancelled = false;
51
- setLoading(true);
52
- setError(undefined);
53
- void Promise.all([listSessions(), listWorkspaces()]).then(([page, workspaces]) => {
54
- if (cancelled)
55
- return;
56
- const fresh = workspaces.find(candidate => candidate.workspaceId === workspace.workspaceId);
57
- const current = fresh ?? workspace;
58
- workspaceRef.current = current;
59
- setRows(ownedItems(page.items, current));
60
- cursorRef.current = page.nextCursor;
61
- setHasMore(page.hasMore);
62
- setLoading(false);
63
- }, (reason) => {
64
- if (cancelled)
65
- return;
66
- setError(errorText(reason));
67
- setLoading(false);
68
- });
69
- return () => { cancelled = true; };
70
- }, [workspace]);
71
- useEffect(() => {
72
- let cancelled = false;
73
- setPresets([]);
74
- setSelectedPreset(undefined);
75
- setPresetsLoading(true);
76
- void listAgentPresets().then((roster) => {
77
- if (cancelled)
78
- return;
79
- const usable = roster.presets.filter(preset => preset.broken === undefined);
80
- setPresets(roster.presets);
81
- setSelectedPreset((usable.find(preset => preset.isDefault) ?? usable[0])?.id);
82
- setPresetsLoading(false);
83
- }, () => {
84
- if (cancelled)
85
- return;
86
- setPresetsLoading(false);
87
- });
88
- return () => { cancelled = true; };
89
- }, [workspace]);
90
- /** Pull the next page and append the rows that belong to this workspace. */
91
- const loadMore = useCallback(() => {
92
- if (busyRef.current)
93
- return;
94
- const cursor = cursorRef.current;
95
- if (cursor === undefined)
96
- return;
97
- busyRef.current = true;
98
- setLoading(true);
99
- void listSessions(cursor).then((page) => {
100
- busyRef.current = false;
101
- setLoading(false);
102
- cursorRef.current = page.nextCursor;
103
- setHasMore(page.hasMore);
104
- setRows(previous => [...previous, ...ownedItems(page.items, workspaceRef.current)]);
105
- }, (reason) => {
106
- busyRef.current = false;
107
- setLoading(false);
108
- setError(errorText(reason));
109
- });
110
- }, [workspace]);
111
- /** Create a blank session in this workspace and open it immediately. */
112
- const handleCreate = useCallback(() => {
113
- if (creating)
114
- return;
115
- setCreating(true);
116
- setCreateError(undefined);
117
- void createSession({
118
- workspaceId: workspace.workspaceId,
119
- ...(selectedPreset !== undefined ? { agentPreset: selectedPreset } : {}),
120
- }).then((created) => {
121
- setCreating(false);
122
- const view = {
123
- sessionId: created.sessionId,
124
- title: '新会话',
125
- updatedAt: Date.now(),
126
- running: false,
127
- blank: true,
128
- };
129
- setRows(previous => [view, ...previous]);
130
- onPick(view);
131
- }, (reason) => {
132
- setCreating(false);
133
- setCreateError(errorText(reason));
134
- });
135
- }, [creating, workspace, onPick, selectedPreset]);
136
- const createHint = createError !== undefined ? staleHostHint(createError) : undefined;
137
- const selectedPresetEntry = presets.find(preset => preset.id === selectedPreset);
138
- return (_jsxs("div", { className: "mobile", 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: workspace.title }), _jsx(ThemeToggle, {})] }), error !== undefined && _jsx("p", { className: "mobile-error mobile-pad", children: error }), _jsxs("div", { className: "mobile-create mobile-pad", children: [presets.length > 0 && (_jsxs("label", { className: "mobile-preset", children: [_jsx("span", { className: "mobile-presetLabel", children: "Agent \u6A21\u5F0F" }), _jsxs("select", { className: "mobile-presetSelect", value: selectedPreset ?? '', disabled: selectedPreset === undefined, onChange: (event) => { setSelectedPreset(event.target.value); }, children: [selectedPreset === undefined && _jsx("option", { value: "", children: "\u65E0\u53EF\u7528 Agent \u6A21\u5F0F" }), presets.map(preset => (_jsxs("option", { value: preset.id, disabled: preset.broken !== undefined, children: [preset.name ?? preset.id, preset.isDefault ? '(默认)' : '', preset.trust === 'user' ? '(本地)' : '', preset.broken !== undefined ? '(不可用)' : ''] }, preset.id)))] })] })), selectedPresetEntry?.description !== undefined && (_jsx("p", { className: "mobile-presetDescription", children: selectedPresetEntry.description })), _jsx("button", { type: "button", className: "mobile-new", disabled: creating || presetsLoading, onClick: () => { void handleCreate(); }, children: creating ? '创建中…' : '+ 新建会话' })] }), createError !== undefined && (_jsxs("p", { className: "mobile-error mobile-pad", children: [createError, createHint !== undefined && _jsx("span", { className: "mobile-hint", children: createHint })] })), _jsx("ul", { className: "mobile-list", children: rows.map(row => (_jsx("li", { children: _jsxs("button", { type: "button", className: "mobile-row", onClick: () => { onPick(row); }, children: [_jsxs("span", { className: "mobile-rowMain", children: [_jsxs("span", { className: "mobile-rowTitle", children: [row.blank ? '新会话' : row.title, row.running ? _jsx("span", { className: "mobile-live", children: "\u25CF" }) : null] }), _jsx("span", { className: "mobile-rowMeta", children: formatTime(row.updatedAt) })] }), _jsx("span", { className: "mobile-chevron", children: "\u203A" })] }) }, row.sessionId))) }), hasMore && (_jsx("div", { className: "mobile-pad", children: _jsx("button", { type: "button", className: "mobile-button mobile-block", disabled: loading, onClick: () => { void loadMore(); }, children: loading ? '加载中…' : '加载更多会话' }) })), !hasMore && rows.length === 0 && !loading && (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u8BE5\u5DE5\u4F5C\u533A\u8FD8\u6CA1\u6709\u4F1A\u8BDD\uFF0C\u70B9\u4E0A\u65B9\u6309\u94AE\u65B0\u5EFA\u4E00\u4E2A" }) }))] }));
139
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=SessionListView.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SessionListView.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/SessionListView.test.tsx"],"names":[],"mappings":""}
@@ -1,167 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- // @vitest-environment jsdom
3
- /** SessionListView: owned-row filtering, incremental pages, session creation. */
4
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
- import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
6
- import { SessionListView } from "./SessionListView.js";
7
- // The api module is fully mocked; the view's App.tsx helpers stay real.
8
- vi.mock('../api.ts', () => ({
9
- listSessions: vi.fn(),
10
- listWorkspaces: vi.fn(),
11
- listAgentPresets: vi.fn(),
12
- createSession: vi.fn(),
13
- }));
14
- import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
15
- const listSessionsMock = vi.mocked(listSessions);
16
- const listWorkspacesMock = vi.mocked(listWorkspaces);
17
- const listAgentPresetsMock = vi.mocked(listAgentPresets);
18
- const createSessionMock = vi.mocked(createSession);
19
- const workspace = {
20
- workspaceId: 'w-1',
21
- path: '/tmp/demo',
22
- title: '演示项目',
23
- sessionIds: ['s-1'],
24
- createdAt: '2026-01-01T00:00:00.000Z',
25
- updatedAt: '2026-01-01T00:00:00.000Z',
26
- };
27
- /** A session.list summary row. */
28
- function summary(sessionId, updatedAt, extra = {}) {
29
- return { sessionId, updatedAt, running: false, blank: false, ...extra };
30
- }
31
- let picked;
32
- function renderList(props = {}) {
33
- picked = undefined;
34
- render(_jsx(SessionListView, { workspace: workspace, onBack: () => { }, onPick: (session) => { picked = session; }, ...props }));
35
- }
36
- beforeEach(() => {
37
- listSessionsMock.mockResolvedValue({ items: [], hasMore: false });
38
- listWorkspacesMock.mockResolvedValue([workspace]);
39
- listAgentPresetsMock.mockResolvedValue({ presets: [], authorable: false, hasDocument: false });
40
- createSessionMock.mockResolvedValue({ sessionId: 's-new' });
41
- });
42
- afterEach(() => {
43
- cleanup();
44
- vi.clearAllMocks();
45
- });
46
- describe('SessionListView roster', () => {
47
- it('renders the sessions owned by the workspace', async () => {
48
- listSessionsMock.mockResolvedValue({
49
- items: [
50
- summary('s-1', 1_700_000_000_000, { projections: { values: { title: '改造移动端' } } }),
51
- summary('s-other', 1_600_000_000_000, { cwd: '/tmp/foreign' }),
52
- ],
53
- hasMore: false,
54
- });
55
- renderList();
56
- expect(await screen.findByText('改造移动端')).toBeTruthy();
57
- // The foreign session (not in workspace.sessionIds) stays hidden.
58
- expect(screen.queryByText(/foreign/)).toBeNull();
59
- });
60
- it('shows the empty state when the workspace owns no sessions', async () => {
61
- renderList();
62
- expect(await screen.findByText(/还没有会话/)).toBeTruthy();
63
- });
64
- it('refreshes the owned-id set from workspace.list on mount', async () => {
65
- listSessionsMock.mockResolvedValue({
66
- items: [summary('s-2', 1_700_000_000_000, { cwd: '/tmp/other' })],
67
- hasMore: false,
68
- });
69
- listWorkspacesMock.mockResolvedValue([
70
- { ...workspace, sessionIds: ['s-2'] },
71
- ]);
72
- renderList();
73
- // s-2 is absent from the stale row but present in the fresh roster.
74
- expect(await screen.findByText(/other/)).toBeTruthy();
75
- });
76
- });
77
- describe('SessionListView creation', () => {
78
- it('creates a blank session in the workspace and opens it immediately', async () => {
79
- renderList();
80
- await screen.findByText(/还没有会话/);
81
- fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
82
- await waitFor(() => {
83
- expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1' });
84
- });
85
- await waitFor(() => {
86
- expect(picked).toBeDefined();
87
- });
88
- expect(picked?.sessionId).toBe('s-new');
89
- expect(picked?.blank).toBe(true);
90
- // The fresh row is prepended without waiting for a refetch.
91
- expect(await screen.findByText('新会话')).toBeTruthy();
92
- });
93
- it('creates the session with the selected agent preset', async () => {
94
- listAgentPresetsMock.mockResolvedValue({
95
- presets: [
96
- { id: 'router-standard', name: 'Router Standard', trust: 'system', isDefault: true },
97
- { id: 'router-spec', name: 'Router Spec', description: '规格驱动模式', trust: 'system', isDefault: false },
98
- ],
99
- authorable: false,
100
- hasDocument: false,
101
- });
102
- renderList();
103
- const picker = await screen.findByRole('combobox', { name: 'Agent 模式' });
104
- expect(picker.value).toBe('router-standard');
105
- fireEvent.change(picker, { target: { value: 'router-spec' } });
106
- fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
107
- await waitFor(() => {
108
- expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1', agentPreset: 'router-spec' });
109
- });
110
- expect(await screen.findByText('规格驱动模式')).toBeTruthy();
111
- });
112
- it('skips a broken default preset and disables it in the picker', async () => {
113
- listAgentPresetsMock.mockResolvedValue({
114
- presets: [
115
- { id: 'broken-default', trust: 'system', isDefault: true, broken: 'missing plugin' },
116
- { id: 'local-router', name: 'Local Router', trust: 'user', isDefault: false },
117
- ],
118
- authorable: false,
119
- hasDocument: false,
120
- });
121
- renderList();
122
- const picker = await screen.findByRole('combobox', { name: 'Agent 模式' });
123
- expect(picker.value).toBe('local-router');
124
- expect(screen.getByRole('option', { name: 'broken-default(默认)(不可用)' }).hasAttribute('disabled')).toBe(true);
125
- expect(screen.getByRole('option', { name: 'Local Router(本地)' })).toBeTruthy();
126
- });
127
- it('keeps default session creation available when the preset roster fails', async () => {
128
- listAgentPresetsMock.mockRejectedValue(new Error('preset roster unavailable'));
129
- renderList();
130
- await screen.findByText(/还没有会话/);
131
- const button = await screen.findByRole('button', { name: '+ 新建会话' });
132
- await waitFor(() => { expect(button.hasAttribute('disabled')).toBe(false); });
133
- fireEvent.click(button);
134
- await waitFor(() => {
135
- expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1' });
136
- });
137
- });
138
- it('waits for the preset roster before enabling session creation', async () => {
139
- let resolvePresets = () => { };
140
- listAgentPresetsMock.mockReturnValue(new Promise(resolve => { resolvePresets = resolve; }));
141
- renderList();
142
- const button = screen.getByRole('button', { name: '+ 新建会话' });
143
- expect(button.hasAttribute('disabled')).toBe(true);
144
- resolvePresets({ presets: [], authorable: false, hasDocument: false });
145
- await waitFor(() => { expect(button.hasAttribute('disabled')).toBe(false); });
146
- });
147
- it('keeps the button disabled while a creation is in flight', async () => {
148
- let resolveCreate = () => { };
149
- createSessionMock.mockReturnValue(new Promise(resolve => { resolveCreate = resolve; }));
150
- renderList();
151
- await screen.findByText(/还没有会话/);
152
- fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
153
- const button = await screen.findByRole('button', { name: '创建中…' });
154
- expect(button.hasAttribute('disabled')).toBe(true);
155
- resolveCreate({ sessionId: 's-new' });
156
- await waitFor(() => { expect(picked).toBeDefined(); });
157
- });
158
- it('shows the stale-host hint when creation is refused with HTTP 403', async () => {
159
- createSessionMock.mockRejectedValue(new Error('HTTP 403'));
160
- renderList();
161
- await screen.findByText(/还没有会话/);
162
- fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
163
- expect(await screen.findByText(/HTTP 403/)).toBeTruthy();
164
- expect(await screen.findByText(/重启 dsh web/)).toBeTruthy();
165
- expect(picked).toBeUndefined();
166
- });
167
- });
@@ -1,21 +0,0 @@
1
- /**
2
- * Landing level: the workspace roster. The mobile surface opens straight
3
- * here — no new-session homepage — and every workspace row is a thin
4
- * fetch from workspace.list (the roster is small; sessions are not loaded
5
- * until a workspace is opened).
6
- */
7
- import type { WorkspaceView as WorkspaceRow } from '@deepseek-ai/dsh-host-apiproxy/api/workspace';
8
- /** Props for the workspace roster. */
9
- export interface WorkspaceViewProps {
10
- /** Workspace carried by the pairing link; opened after the roster loads. */
11
- initialWorkspaceId?: string;
12
- /** Open one workspace's session list. */
13
- onPick(workspace: WorkspaceRow): void;
14
- }
15
- /**
16
- * Render the workspace roster.
17
- * @param props - the pick action.
18
- * @returns the roster.
19
- */
20
- export declare function WorkspaceView({ initialWorkspaceId, onPick }: WorkspaceViewProps): import("react").JSX.Element;
21
- //# sourceMappingURL=WorkspaceView.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspaceView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/WorkspaceView.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,IAAI,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAwHjG,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,yCAAyC;IACzC,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI,CAAA;CACtC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,kBAAkB,EAAE,MAAM,EAAE,EAAE,kBAAkB,+BAuG/E"}
@@ -1,93 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * Landing level: the workspace roster. The mobile surface opens straight
4
- * here — no new-session homepage — and every workspace row is a thin
5
- * fetch from workspace.list (the roster is small; sessions are not loaded
6
- * until a workspace is opened).
7
- */
8
- import { useEffect, useState } from 'react';
9
- import { listWorkspaces, listDirectory, createWorkspace } from "../api.js";
10
- import { errorText } from "./App.js";
11
- import { ThemeToggle } from "../theme-toggle.js";
12
- /**
13
- * Render the directory browser for creating a new workspace.
14
- */
15
- function DirectoryBrowser({ onCancel, onPick }) {
16
- const [listing, setListing] = useState(undefined);
17
- const [error, setError] = useState(undefined);
18
- const [creating, setCreating] = useState(false);
19
- const [path, setPath] = useState(undefined);
20
- const [reload, setReload] = useState(0);
21
- useEffect(() => {
22
- let cancelled = false;
23
- setError(undefined);
24
- setListing(undefined);
25
- void listDirectory(path).then((res) => {
26
- if (cancelled)
27
- return;
28
- setListing(res);
29
- }, (reason) => {
30
- if (cancelled)
31
- return;
32
- setError(errorText(reason));
33
- });
34
- return () => { cancelled = true; };
35
- }, [path, reload]);
36
- const handleCreate = async () => {
37
- if (listing === undefined || creating)
38
- return;
39
- setCreating(true);
40
- setError(undefined);
41
- try {
42
- const result = await createWorkspace(listing.path);
43
- onPick(result.workspace);
44
- }
45
- catch (reason) {
46
- setCreating(false);
47
- setError(errorText(reason));
48
- }
49
- };
50
- return (_jsxs("div", { className: "mobile dir-browser", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("button", { type: "button", className: "mobile-headerAction", onClick: onCancel, children: "\u8FD4\u56DE" }), _jsx("h1", { className: "mobile-title", children: "\u9009\u62E9\u76EE\u5F55" })] }), listing !== undefined && (_jsx("div", { className: "dir-crumbs", children: listing.crumbs.map((crumb, idx) => (_jsxs("span", { children: [_jsx("button", { type: "button", className: "dir-crumb", onClick: () => setPath(crumb.path), children: crumb.name || '/' }), idx < listing.crumbs.length - 1 && _jsx("span", { className: "dir-crumb-separator", children: "/" })] }, crumb.path))) })), error !== undefined ? (_jsxs("div", { className: "mobile-empty", children: [_jsx("p", { className: "mobile-error", children: error }), _jsx("button", { type: "button", className: "mobile-button", onClick: () => setReload(n => n + 1), children: "\u91CD\u8BD5" })] })) : listing === undefined ? (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u52A0\u8F7D\u4E2D\u2026" }) })) : (_jsx("ul", { className: "mobile-list", children: listing.entries.length === 0 ? (_jsx("div", { className: "mobile-empty dir-empty", children: _jsx("p", { className: "mobile-muted", children: "\u7A7A\u76EE\u5F55" }) })) : (listing.entries.map((entry) => (_jsx("li", { children: _jsx("button", { type: "button", className: `mobile-row dir-entry ${entry.hidden ? 'dir-entry-hidden' : ''}`, onClick: () => setPath(entry.path), children: _jsx("span", { className: "mobile-rowTitle", children: entry.name }) }) }, entry.path)))) })), _jsx("div", { className: "dir-select", children: _jsx("button", { type: "button", className: "mobile-button", disabled: listing === undefined || creating, onClick: () => void handleCreate(), children: creating ? '创建中…' : '选择此目录' }) })] }));
51
- }
52
- /**
53
- * Render the workspace roster.
54
- * @param props - the pick action.
55
- * @returns the roster.
56
- */
57
- export function WorkspaceView({ initialWorkspaceId, onPick }) {
58
- const [items, setItems] = useState(undefined);
59
- const [error, setError] = useState(undefined);
60
- // Bumped by the retry button to re-run the roster fetch effect.
61
- const [reload, setReload] = useState(0);
62
- const [isCreating, setIsCreating] = useState(false);
63
- useEffect(() => {
64
- let cancelled = false;
65
- void listWorkspaces().then((rows) => {
66
- if (cancelled)
67
- return;
68
- const target = initialWorkspaceId === undefined
69
- ? undefined
70
- : rows.find(workspace => workspace.workspaceId === initialWorkspaceId);
71
- if (target !== undefined) {
72
- onPick(target);
73
- return;
74
- }
75
- setItems(rows);
76
- }, (reason) => {
77
- if (cancelled)
78
- return;
79
- setError(errorText(reason));
80
- });
81
- return () => { cancelled = true; };
82
- }, [initialWorkspaceId, onPick, reload]);
83
- if (isCreating) {
84
- return (_jsx(DirectoryBrowser, { onCancel: () => setIsCreating(false), onPick: onPick }));
85
- }
86
- if (error !== undefined) {
87
- return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), _jsxs("div", { className: "mobile-empty", children: [_jsxs("p", { className: "mobile-error", children: ["\u52A0\u8F7D\u5931\u8D25\uFF1A", error] }), _jsx("button", { type: "button", className: "mobile-button", onClick: () => { setError(undefined); setItems(undefined); setReload(n => n + 1); }, children: "\u91CD\u8BD5" })] })] }));
88
- }
89
- if (items === undefined) {
90
- return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), _jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u52A0\u8F7D\u4E2D\u2026" }) })] }));
91
- }
92
- return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), items.length === 0 ? (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u6682\u65E0\u5DE5\u4F5C\u533A" }) })) : (_jsx("ul", { className: "mobile-list", children: items.map(workspace => (_jsx("li", { children: _jsxs("button", { type: "button", className: "mobile-row", onClick: () => { onPick(workspace); }, children: [_jsx("span", { className: "mobile-rowTitle", children: workspace.title }), _jsx("span", { className: "mobile-rowMeta", children: workspace.path }), _jsx("span", { className: "mobile-chevron", children: "\u203A" })] }) }, workspace.workspaceId))) })), _jsx("div", { style: { padding: '16px' }, children: _jsx("button", { type: "button", className: "mobile-button", onClick: () => setIsCreating(true), children: "+ \u65B0\u5EFA\u5DE5\u4F5C\u533A" }) })] }));
93
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=WorkspaceView.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspaceView.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/WorkspaceView.test.tsx"],"names":[],"mappings":""}