feat(desktop): Chinese menu, branded icon, and a self-contained harness bundle
Localizes the Electron application menu (文件/编辑/视图/窗口/帮助 plus the macOS app menu), adds a branded 1024px app icon (window + per-target icns/ico), and makes the packaged app fully self-contained. Because the harness's pnpm workspace does not cleanly materialize via pnpm deploy or electron-builder's dependency resolution (per-package symlinks to vendored sources, native addons, a separate frontend dist), scripts/build-harness.mjs assembles the repository's working runtime — node_modules, vendor, packages, native, apps/cli, apps/web, and a bundled Node binary — into build/harness. electron-builder ships it as an extraResource at Resources/harness, and the main process spawns that bundled node + dsh entry when packaged (keeping the system-Node child during dev). Adds a comprehensive README and ignores the multi-GB harness/dist from git. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,10 @@ python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/
|
||||
python/**/__pycache__/
|
||||
python/**/.pytest_cache/
|
||||
apps/web/dist/
|
||||
# Packaged desktop app output and the generated self-contained harness runtime
|
||||
# (build/harness is a multi-GB copy of node_modules + vendor + native + Node).
|
||||
apps/desktop/dist/
|
||||
apps/desktop/build/harness/
|
||||
.artifacts/
|
||||
.playwright-mcp/
|
||||
.orig
|
||||
|
||||
+112
-11
@@ -1,13 +1,51 @@
|
||||
# @deepseek-ai/dsh-desktop
|
||||
|
||||
Electron desktop shell over the DeepSeek Harness. The Electron main process is a
|
||||
thin wrapper: it spawns the real `dsh` CLI running the `web` profile on loopback
|
||||
(an OS-assigned port), parses the readiness URL line the profile prints
|
||||
Electron desktop shell for DeepSeek Harness. The Electron main process is a thin
|
||||
wrapper: it spawns the real `dsh` CLI running the `web` profile on loopback (an
|
||||
OS-assigned port), parses the readiness URL line the profile prints
|
||||
(`dsh web: http://127.0.0.1:<port>`), and opens a native window at that address.
|
||||
|
||||
The harness — its Cordis plugins, webserver, static frontend dist, and WebSocket
|
||||
transport — runs exactly as `dsh web` would, as a separate system-Node process,
|
||||
so native addons keep their system Node ABI.
|
||||
so native addons keep their system Node ABI and are never loaded into Electron.
|
||||
|
||||
## Why a system-Node child
|
||||
|
||||
Electron's bundled Node ABI differs from the system Node the harness's native
|
||||
addons (e.g. `node-pty` for the terminal capability) are built against. Running
|
||||
the harness in its own `node` child keeps those addons on the system Node ABI;
|
||||
the Electron main and renderer never load them. This is why the desktop is a
|
||||
launcher, not a bundler: it delegates the whole harness runtime to a child
|
||||
process and only opens a window at the served URL.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Electron main (this package)
|
||||
│ spawns node <harness>/apps/cli/lib/bin.js --profile web --host 127.0.0.1 --port 0
|
||||
▼
|
||||
system-Node child — the dsh harness
|
||||
│ prints "dsh web: http://127.0.0.1:<port>" on stdout
|
||||
▼
|
||||
Electron opens a BrowserWindow at that loopback URL
|
||||
```
|
||||
|
||||
The main process keeps the window bound to the child: it reads the child's
|
||||
stdout for the readiness line, opens the window once the port is known, and
|
||||
quits when the child exits. A fatal child failure is shown in a native error
|
||||
box.
|
||||
|
||||
## Feature surface
|
||||
|
||||
- **Chinese application menu** — replaces Electron's English default with
|
||||
文件/编辑/视图/窗口/帮助 (plus the macOS app menu: 关于/服务/隐藏/退出). Menu
|
||||
items are Electron *roles* with localized labels, so accelerators stay the
|
||||
system defaults (e.g. `Cmd+R` reload, `Cmd+Alt+I` DevTools).
|
||||
- **Branded icon** — `build/icon.png` (1024px) is used for the window icon and,
|
||||
through `electron-builder.yml`, derived into the per-target `.icns`/`.ico`.
|
||||
Replace it with final artwork anytime.
|
||||
- **Self-contained harness** — the packaged app ships the full harness runtime
|
||||
under `Contents/Resources/harness` (see below), so it runs on any target
|
||||
machine with no external Node or repository install.
|
||||
|
||||
## Run from a checkout
|
||||
|
||||
@@ -18,14 +56,77 @@ pnpm desktop:dev # opens the desktop window
|
||||
```
|
||||
|
||||
`desktop:dev` builds the frontend dist and the `dsh` CLI, then launches
|
||||
Electron. Override the spawned Node or `dsh` entry with `DSH_NODE` / `DSH_ENTRY`.
|
||||
Electron. In development the harness resolves from the workspace, and you can
|
||||
override the spawned Node or `dsh` entry with `DSH_NODE` / `DSH_ENTRY`.
|
||||
|
||||
## Package
|
||||
## Packaging
|
||||
|
||||
```sh
|
||||
pnpm desktop:pack # electron-builder: mac .dmg / win .nsis in apps/desktop/dist
|
||||
pnpm --filter @deepseek-ai/dsh-desktop run build:harness # (re)assemble the bundled runtime
|
||||
pnpm desktop:pack # electron-builder: mac .dmg / win .nsis
|
||||
```
|
||||
|
||||
Packaging is unsigned by default; provide a certificate to distribute. The
|
||||
harness child needs a Node runtime on the target platform — see
|
||||
`electron-builder.yml` and the notes in `src/main.ts`.
|
||||
Artifacts land in `apps/desktop/dist`.
|
||||
|
||||
### The self-contained harness (`build/harness`)
|
||||
|
||||
The harness's pnpm workspace does **not** cleanly materialize into a portable
|
||||
node_modules: its packages are linked through per-package symlinks to vendored
|
||||
sources at the repository root (`vendor/`, `packages/`, `native/`), it has
|
||||
native addons built for a specific Node ABI, and the web profile serves a
|
||||
separately built frontend dist. `pnpm deploy` and electron-builder's dependency
|
||||
resolution both produce incomplete or broken copies of this runtime.
|
||||
|
||||
The only known-good runtime is the repository's own working tree. So
|
||||
`scripts/build-harness.mjs` assembles a self-contained copy into
|
||||
`apps/desktop/build/harness`, preserving the relative layout the symlinks
|
||||
depend on:
|
||||
|
||||
```
|
||||
build/harness/
|
||||
node_modules/ the full dependency store + link network
|
||||
vendor/ vendored Cordis packages the symlinks resolve to
|
||||
packages/ workspace packages the symlinks resolve to
|
||||
native/ native addon sources (landlock-run)
|
||||
apps/cli/ the dsh CLI (the harness entry)
|
||||
apps/web/ the frontend + built dist
|
||||
bin/node the platform Node binary the child runs on
|
||||
```
|
||||
|
||||
electron-builder ships this as an `extraResource` at
|
||||
`Contents/Resources/harness`, and `src/main.ts` spawns
|
||||
`Resources/harness/bin/node` + `Resources/harness/apps/cli/lib/bin.js` with the
|
||||
harness root as the child working directory. Because every symlink is relative
|
||||
to that tree, the bundled runtime resolves identically on any machine of the
|
||||
same OS/arch.
|
||||
|
||||
### Platform matrix
|
||||
|
||||
Each target platform needs its own harness: the bundled `bin/node` and the
|
||||
native addons are OS/arch-specific. Regenerate `build/harness` on each target
|
||||
platform (or per-target in CI) before packaging that platform. The current
|
||||
configuration targets **macOS arm64** (`mac.target: dmg`); `win.target: nsis`
|
||||
is declared but needs a Windows-built harness.
|
||||
|
||||
### Signing
|
||||
|
||||
No signing is configured (`electron-builder.yml` has no `mac.sign` identity).
|
||||
Artifacts are unsigned: on first open, macOS Gatekeeper may block them (open
|
||||
via right-click → Open, or add a `Developer ID Application` certificate to
|
||||
distribute). Provide a certificate before public distribution.
|
||||
|
||||
### Size
|
||||
|
||||
The bundled harness is multi-GB uncompressed (≈2 GB), dominated by
|
||||
`node_modules`; the compressed `.dmg` is on the order of a few hundred MB to
|
||||
~1 GB. This is the inherent footprint of shipping the full harness runtime
|
||||
standalone, and is the accepted trade-off for a zero-external-dependency app.
|
||||
|
||||
## Notes
|
||||
|
||||
- `asar: false` and `npmRebuild: false` are deliberate (see
|
||||
`electron-builder.yml` comments): the harness stays as real on-disk files for
|
||||
the child and its native addons, and native addons are not rebuilt for the
|
||||
Electron ABI because they never load under Electron.
|
||||
- The profile data (`~/.dsh/profiles/web`) is created and read on the host the
|
||||
app runs on; it is not part of the packaged artifact.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 834 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#0d3b2e"/>
|
||||
<stop offset="1" stop-color="#14915c"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1024" height="1024" fill="url(#bg)"/>
|
||||
<g transform="translate(205 286) scale(26.5)" fill="#ffffff">
|
||||
<path d="M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -8,24 +8,41 @@
|
||||
# only for local evaluation until a certificate is provided.
|
||||
appId: ai.deepseek.harness
|
||||
productName: DeepSeek Harness
|
||||
# Native addons (node-pty, …) run under the system-Node child process, never
|
||||
# under Electron, so skip @electron/rebuild: rebuilding them for the Electron
|
||||
# ABI is wasted work and needs a headers download the local proxy rejects.
|
||||
npmRebuild: false
|
||||
# The harness runs as an external system-Node child process (see src/main.ts).
|
||||
# The fully self-contained runtime lives in build/harness — a copy of the
|
||||
# repository's working node_modules plus vendor/packages/native/apps and a
|
||||
# bundled Node binary — and is shipped as extraResources so the packaged app
|
||||
# runs on any machine with no external install. The Electron shell itself is
|
||||
# unpacked (`asar: false`); the harness stays on disk as real files for the
|
||||
# child and for the native addons it dlopens.
|
||||
asar: false
|
||||
directories:
|
||||
output: dist
|
||||
files:
|
||||
- lib/types/main.js
|
||||
- lib/types/**/*.js
|
||||
- package.json
|
||||
# The self-contained harness (build/harness) lands at Contents/Resources/harness
|
||||
# in the packaged app; src/main.ts spawns the bundled node + dsh CLI from there.
|
||||
extraResources:
|
||||
- from: build/harness
|
||||
to: harness
|
||||
filter:
|
||||
- "**/*"
|
||||
# One 1024px build/icon.png; electron-builder derives the per-target .icns/.ico.
|
||||
icon: build/icon.png
|
||||
mac:
|
||||
target:
|
||||
- dmg
|
||||
icon: build/icon.png
|
||||
category: public.app-category.developer-tools
|
||||
win:
|
||||
target:
|
||||
- nsis
|
||||
icon: build/icon.png
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
# Native addons the harness loads (e.g. the PTY node-pty) must live outside the
|
||||
# asar archive so the spawned system-Node process can dlopen them by path.
|
||||
asarUnpack:
|
||||
- '**/*.node'
|
||||
- 'node_modules/@deepseek-ai/dsh/node_modules/**/*.node'
|
||||
- 'node_modules/node-pty/**'
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"version": "0.1.0-rc.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"author": "PineSound",
|
||||
"main": "lib/types/main.js",
|
||||
"files": [
|
||||
"lib/types/*.js",
|
||||
@@ -12,6 +13,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -b tsconfig.json",
|
||||
"build:harness": "node scripts/build-harness.mjs",
|
||||
"typecheck": "tsc -b tsconfig.json",
|
||||
"test": "vitest run",
|
||||
"pack": "electron-builder"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Assemble the self-contained harness runtime for the packaged app.
|
||||
*
|
||||
* The harness's pnpm workspace does not cleanly materialize via `pnpm deploy` or
|
||||
* electron-builder's dependency resolution (per-package symlinks to vendored
|
||||
* packages, native addons, a separate frontend dist). The only known-good
|
||||
* runtime is the repository's own working tree, so this copies the parts the
|
||||
* harness needs at the same relative layout — node_modules, vendor, packages,
|
||||
* native, apps/cli, apps/web — plus the platform Node binary, into
|
||||
* `build/harness`, which electron-builder ships as `extraResources`.
|
||||
*
|
||||
* Run from the repository root before `desktop:pack`.
|
||||
*/
|
||||
|
||||
import { cpSync, chmodSync, existsSync, mkdirSync, rmSync } from 'node:fs'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const out = join(root, 'apps/desktop/build/harness')
|
||||
|
||||
// Dirs whose relative layout must be preserved so node_modules symlinks resolve.
|
||||
const harnessDirs = ['node_modules', 'vendor', 'packages', 'native']
|
||||
const extraDirs = [['apps/cli', 'apps/cli'], ['apps/web', 'apps/web']]
|
||||
|
||||
rmSync(out, { recursive: true, force: true })
|
||||
mkdirSync(join(out, 'apps'), { recursive: true })
|
||||
mkdirSync(join(out, 'bin'), { recursive: true })
|
||||
|
||||
for (const d of harnessDirs) {
|
||||
const src = join(root, d)
|
||||
if (existsSync(src)) cpSync(src, join(out, d), { recursive: true })
|
||||
}
|
||||
for (const [src, dst] of extraDirs) {
|
||||
cpSync(join(root, src), join(out, dst), { recursive: true })
|
||||
}
|
||||
|
||||
// Bundle the platform Node binary for the harness child.
|
||||
const nodeBin = execFileSync('node', ['-e', 'process.stdout.write(process.execPath)']).toString()
|
||||
if (!existsSync(nodeBin)) throw new Error(`node executable not found: ${nodeBin}`)
|
||||
cpSync(nodeBin, join(out, 'bin/node'))
|
||||
chmodSync(join(out, 'bin/node'), 0o755)
|
||||
|
||||
console.log(`assembled self-contained harness at ${out}`)
|
||||
+113
-5
@@ -13,22 +13,123 @@
|
||||
*/
|
||||
|
||||
import { spawn, type ChildProcess } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { resolve } from 'node:path'
|
||||
import { app, dialog, BrowserWindow } from 'electron'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { app, dialog, BrowserWindow, Menu, type MenuItemConstructorOptions } from 'electron'
|
||||
|
||||
import { LOOPBACK_HOST, parseReadyPort } from './ready-port.ts'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
/** User-facing product name (the npm name is the scoped package id). */
|
||||
const PRODUCT_NAME = 'DeepSeek Harness'
|
||||
|
||||
/** Window icon: the branded PNG in build/, resolved from this ES module's URL. */
|
||||
const APP_ICON = fileURLToPath(new URL('../../build/icon.png', import.meta.url))
|
||||
|
||||
/**
|
||||
* The self-contained harness runtime bundled into the packaged app by
|
||||
* electron-builder's `extraResources` (see electron-builder.yml): a copy of the
|
||||
* repository's working node_modules plus vendor/packages/native/apps and a
|
||||
* bundled Node binary, so the harness child runs on any machine with no
|
||||
* external install. In development the harness resolves from the workspace.
|
||||
*/
|
||||
function harnessRoot(): string | undefined {
|
||||
return app.isPackaged ? join(process.resourcesPath, 'harness') : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a Chinese application menu, replacing Electron's English default.
|
||||
* Roles carry the platform behavior (close, quit, zoom, DevTools, …); only the
|
||||
* visible labels are localized, so the accelerators stay the system defaults.
|
||||
*/
|
||||
function installAppMenu(): void {
|
||||
const isMac = process.platform === 'darwin'
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
...(isMac
|
||||
? [{
|
||||
label: PRODUCT_NAME,
|
||||
submenu: [
|
||||
{ role: 'about', label: `关于 ${PRODUCT_NAME}` },
|
||||
{ type: 'separator' },
|
||||
{ role: 'services', label: '服务' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'hide', label: `隐藏 ${PRODUCT_NAME}` },
|
||||
{ role: 'hideOthers', label: '隐藏其他' },
|
||||
{ role: 'unhide', label: '全部显示' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit', label: `退出 ${PRODUCT_NAME}` },
|
||||
],
|
||||
}] satisfies MenuItemConstructorOptions[]
|
||||
: []),
|
||||
{
|
||||
label: '文件',
|
||||
submenu: [
|
||||
isMac
|
||||
? { role: 'close', label: '关闭窗口' }
|
||||
: { role: 'quit', label: '退出' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ role: 'undo', label: '撤销' },
|
||||
{ role: 'redo', label: '重做' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut', label: '剪切' },
|
||||
{ role: 'copy', label: '复制' },
|
||||
{ role: 'paste', label: '粘贴' },
|
||||
{ role: 'selectAll', label: '全选' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '视图',
|
||||
submenu: [
|
||||
{ role: 'reload', label: '重新加载' },
|
||||
{ role: 'forceReload', label: '强制重新加载' },
|
||||
{ role: 'toggleDevTools', label: '开发者工具' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetZoom', label: '实际大小' },
|
||||
{ role: 'zoomIn', label: '放大' },
|
||||
{ role: 'zoomOut', label: '缩小' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'togglefullscreen', label: '切换全屏' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
submenu: [
|
||||
{ role: 'minimize', label: '最小化' },
|
||||
{ role: 'zoom', label: '缩放' },
|
||||
...(isMac
|
||||
? [{ type: 'separator' }, { role: 'front', label: '前置全部窗口' }] satisfies MenuItemConstructorOptions[]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '帮助',
|
||||
submenu: [
|
||||
{ role: 'about', label: `关于 ${PRODUCT_NAME}` },
|
||||
],
|
||||
},
|
||||
]
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
}
|
||||
|
||||
/** Spawn's node executable: a bundled one when the app is packaged, else PATH. */
|
||||
function nodeExecutable(): string {
|
||||
const harness = harnessRoot()
|
||||
if (harness !== undefined) return join(harness, 'bin/node')
|
||||
if (process.env.DSH_NODE) return resolve(process.env.DSH_NODE)
|
||||
return 'node'
|
||||
}
|
||||
|
||||
/** The dsh CLI entry to spawn: an explicit dev override, else the built bin. */
|
||||
/** The dsh CLI entry to spawn: the bundled bin when packaged, else the built bin. */
|
||||
function dshEntry(): string {
|
||||
const harness = harnessRoot()
|
||||
if (harness !== undefined) return join(harness, 'apps/cli/lib/bin.js')
|
||||
if (process.env.DSH_ENTRY) return resolve(process.env.DSH_ENTRY)
|
||||
return require.resolve('@deepseek-ai/dsh/lib/bin.js')
|
||||
}
|
||||
@@ -38,7 +139,10 @@ function openWindow(url: string): BrowserWindow {
|
||||
const win = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 840,
|
||||
title: 'DeepSeek Harness',
|
||||
title: PRODUCT_NAME,
|
||||
// The window icon matters on Linux/Windows; on macOS the dock and menu bar
|
||||
// use the packaged .app icon from electron-builder's build/icon.png.
|
||||
...(existsSync(APP_ICON) ? { icon: APP_ICON } : {}),
|
||||
webPreferences: {
|
||||
// The UI is a remote SPA served by the harness; keep Node out of it.
|
||||
nodeIntegration: false,
|
||||
@@ -61,14 +165,17 @@ let session: Session | undefined
|
||||
|
||||
/** Fail loudly in the shell when the harness cannot start. */
|
||||
function reportFatal(error: unknown): void {
|
||||
void dialog.showErrorBox('DeepSeek Harness failed to start', String(error))
|
||||
void dialog.showErrorBox(`${PRODUCT_NAME} 启动失败`, String(error))
|
||||
app.exit(1)
|
||||
}
|
||||
|
||||
/** Spawn the web profile and open a window once its URL is known. */
|
||||
function startSession(): void {
|
||||
const entry = dshEntry()
|
||||
const harness = harnessRoot()
|
||||
const child = spawn(nodeExecutable(), [entry, '--profile', 'web', '--host', LOOPBACK_HOST, '--port', '0'], {
|
||||
// Run the harness from its own root so relative path resolution is stable.
|
||||
cwd: harness,
|
||||
stdio: ['ignore', 'pipe', 'inherit'],
|
||||
env: { ...process.env, DSH_TELEMETRY_DISABLED: '1' },
|
||||
})
|
||||
@@ -107,6 +214,7 @@ function startSession(): void {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
installAppMenu()
|
||||
startSession()
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0 && session !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user