@lijian-ui/dsh-term 0.2.0 → 0.3.1
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/lib/client.js +475 -94
- package/lib/client.js.map +1 -1
- package/lib/index.js +454 -29
- package/lib/tsconfig.client.tsbuildinfo +1 -1
- package/lib/tsconfig.host.tsbuildinfo +1 -1
- package/lib/types/client/client-i18n.d.ts +29 -0
- package/lib/types/client/client-i18n.d.ts.map +1 -0
- package/lib/types/client/i18n-seat.d.ts +15 -0
- package/lib/types/client/i18n-seat.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/term/AnimatedDock.d.ts.map +1 -1
- package/lib/types/client/term/TerminalPanel.d.ts +8 -3
- package/lib/types/client/term/TerminalPanel.d.ts.map +1 -1
- package/lib/types/core/types.d.ts +1 -0
- package/lib/types/core/types.d.ts.map +1 -1
- package/lib/types/gateway/i18n.d.ts +17 -0
- package/lib/types/gateway/i18n.d.ts.map +1 -0
- package/lib/types/host/routes.d.ts +3 -1
- package/lib/types/host/routes.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/client-i18n.ts +68 -0
- package/src/client/i18n-seat.ts +27 -0
- package/src/client/index.ts +22 -3
- package/src/client/term/AnimatedDock.tsx +3 -1
- package/src/client/term/DockItem.tsx +1 -1
- package/src/client/term/TerminalPanel.tsx +333 -50
- package/src/client/term/animated-dock.module.css +3 -3
- package/src/client/term/api.ts +17 -2
- package/src/client/term/chat-helper.ts +28 -0
- package/src/client/term/term.module.css +155 -18
- package/src/core/types.ts +22 -4
- package/src/gateway/i18n.ts +67 -0
- package/src/host/pty-service.ts +160 -17
- package/src/host/routes.ts +44 -4
- package/src/index.ts +31 -1
package/src/host/routes.ts
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
11
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
12
|
-
import type { TermEvent, TermSpawnRequest } from '../core/types.ts'
|
|
12
|
+
import type { ShellType, TermEvent, TermSpawnRequest } from '../core/types.ts'
|
|
13
13
|
import type { PtyService } from './pty-service.ts'
|
|
14
14
|
import { isLoopbackRequest } from './loopback.ts'
|
|
15
|
+
import type { Translator } from '../gateway/i18n.ts'
|
|
15
16
|
|
|
16
17
|
/** JSON envelope mirrors the file-manager panel shape. */
|
|
17
18
|
type Envelope<T> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }
|
|
@@ -63,9 +64,10 @@ function json(res: ServerResponse, envelope: Envelope<unknown>, status = 200): v
|
|
|
63
64
|
* Register the /dsh-term routes.
|
|
64
65
|
* @param ctx - context carrying the webServer service.
|
|
65
66
|
* @param pty - the session registry.
|
|
67
|
+
* @param getT - lazy translator getter (reflects current language).
|
|
66
68
|
* @returns route disposers.
|
|
67
69
|
*/
|
|
68
|
-
export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
|
|
70
|
+
export function registerTermRoutes(ctx: Context, pty: PtyService, getT: () => Translator): () => void {
|
|
69
71
|
const subscribers = new Set<Subscriber>()
|
|
70
72
|
const push = (event: TermEvent): void => {
|
|
71
73
|
for (const subscriber of subscribers) {
|
|
@@ -74,7 +76,12 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
pty.onOutput = (id, data) => push({ kind: 'output', id, data })
|
|
77
|
-
pty.onExit = (id, exitCode) =>
|
|
79
|
+
pty.onExit = (id, exitCode) => {
|
|
80
|
+
const msg = getT().t('msg.sessionExited', exitCode)
|
|
81
|
+
push({ kind: 'exit', id, exitCode, message: msg })
|
|
82
|
+
}
|
|
83
|
+
pty.onDetach = (id) => push({ kind: 'detached', id })
|
|
84
|
+
pty.onReattach = (session) => push({ kind: 'reattached', session })
|
|
78
85
|
|
|
79
86
|
const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
80
87
|
if (!isLoopbackRequest(req)) {
|
|
@@ -87,6 +94,11 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
|
|
|
87
94
|
json(res, OK({ sessions: pty.list() }))
|
|
88
95
|
return
|
|
89
96
|
}
|
|
97
|
+
if (req.method === 'GET' && url.pathname === '/dsh-term/shells') {
|
|
98
|
+
const shells = await pty.detectShells()
|
|
99
|
+
json(res, OK({ shells }))
|
|
100
|
+
return
|
|
101
|
+
}
|
|
90
102
|
if (req.method !== 'POST') {
|
|
91
103
|
json(res, MALFORMED, 405)
|
|
92
104
|
return
|
|
@@ -105,13 +117,18 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
|
|
|
105
117
|
json(res, MALFORMED, 400)
|
|
106
118
|
return
|
|
107
119
|
}
|
|
120
|
+
const validShells: readonly ShellType[] = ['bash', 'zsh', 'powershell', 'cmd', 'gitbash']
|
|
108
121
|
const session = pty.spawn({
|
|
109
122
|
name: typeof request.name === 'string' ? request.name : undefined,
|
|
110
123
|
cwd: typeof request.cwd === 'string' ? request.cwd : undefined,
|
|
111
|
-
shell: typeof request.shell === 'string'
|
|
124
|
+
shell: typeof request.shell === 'string' && validShells.includes(request.shell as ShellType)
|
|
125
|
+
? request.shell as ShellType : undefined,
|
|
112
126
|
args: Array.isArray(request.args) ? request.args.filter((a): a is string => typeof a === 'string') : undefined,
|
|
113
127
|
cols: typeof request.cols === 'number' ? request.cols : undefined,
|
|
114
128
|
rows: typeof request.rows === 'number' ? request.rows : undefined,
|
|
129
|
+
env: typeof request.env === 'object' && request.env !== null
|
|
130
|
+
? Object.fromEntries(Object.entries(request.env).filter(([, v]) => typeof v === 'string')) as Record<string, string>
|
|
131
|
+
: undefined,
|
|
115
132
|
})
|
|
116
133
|
push({ kind: 'start', session })
|
|
117
134
|
json(res, OK(session))
|
|
@@ -153,6 +170,29 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
|
|
|
153
170
|
json(res, OK({ ok: pty.close(body.id) }))
|
|
154
171
|
return
|
|
155
172
|
}
|
|
173
|
+
case '/dsh-term/detach': {
|
|
174
|
+
const body = payload as { id?: unknown }
|
|
175
|
+
if (typeof body?.id !== 'string') {
|
|
176
|
+
json(res, MALFORMED, 400)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
json(res, OK({ ok: pty.detach(body.id) }))
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
case '/dsh-term/reattach': {
|
|
183
|
+
const body = payload as { id?: unknown }
|
|
184
|
+
if (typeof body?.id !== 'string') {
|
|
185
|
+
json(res, MALFORMED, 400)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
const session = pty.reattach(body.id)
|
|
189
|
+
if (session === null) {
|
|
190
|
+
json(res, FAIL('session not found', 'not_found'), 404)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
json(res, OK(session))
|
|
194
|
+
return
|
|
195
|
+
}
|
|
156
196
|
default:
|
|
157
197
|
json(res, MALFORMED, 404)
|
|
158
198
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Context } from '@deepseek-ai/cordis'
|
|
9
9
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
10
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
10
11
|
import { mountOnce } from './mount-once.ts'
|
|
11
12
|
import { PtyService } from './host/pty-service.ts'
|
|
12
13
|
import { registerTermRoutes } from './host/routes.ts'
|
|
14
|
+
import { createTranslator, type Lang } from './gateway/i18n.ts'
|
|
13
15
|
|
|
14
16
|
/** Required services: the route registry. */
|
|
15
17
|
export const inject = ['webServer']
|
|
@@ -25,13 +27,41 @@ export const apply = mountOnce('@lijian-ui/dsh-term', applyImpl)
|
|
|
25
27
|
|
|
26
28
|
function applyImpl(ctx: Context): void {
|
|
27
29
|
const pty = new PtyService()
|
|
30
|
+
|
|
31
|
+
/** Current language; resolved from dsh global settings. */
|
|
32
|
+
let lang: Lang = 'zh'
|
|
33
|
+
const t = createTranslator(lang)
|
|
34
|
+
|
|
35
|
+
/** Resolve the user's language preference from dsh settings. */
|
|
36
|
+
function resolveLang(): Lang {
|
|
37
|
+
try {
|
|
38
|
+
const settings = ctx.get('settings') as
|
|
39
|
+
| { get(ns: unknown): { preference?: string } | undefined }
|
|
40
|
+
| undefined
|
|
41
|
+
if (!settings) return 'zh'
|
|
42
|
+
const section = settings.get(settingsNamespace('locale'))
|
|
43
|
+
return section?.preference === 'en' ? 'en' : 'zh'
|
|
44
|
+
} catch {
|
|
45
|
+
return 'zh'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
// Route registration + pty teardown both ride the effect fiber: the effect
|
|
29
50
|
// callback runs immediately and its return value is the fiber disposer.
|
|
30
51
|
ctx.effect(() => {
|
|
31
|
-
|
|
52
|
+
lang = resolveLang()
|
|
53
|
+
// t is a closure over lang; no separate sync needed.
|
|
54
|
+
const disposeRoutes = registerTermRoutes(ctx, pty, () => createTranslator(lang))
|
|
32
55
|
return () => {
|
|
33
56
|
disposeRoutes()
|
|
34
57
|
pty.dispose()
|
|
35
58
|
}
|
|
36
59
|
}, 'dsh-term: routes + pty lifecycle')
|
|
60
|
+
|
|
61
|
+
// Listen for global language changes (dsh settings → General → Language).
|
|
62
|
+
ctx.root.on('settings/updated', (ns, next) => {
|
|
63
|
+
if (ns !== settingsNamespace('locale')) return
|
|
64
|
+
const pref = (next as { preference?: string })?.preference
|
|
65
|
+
lang = pref === 'en' ? 'en' : 'zh'
|
|
66
|
+
})
|
|
37
67
|
}
|