@yfwu2020/dsh-selection-explain 0.10.4 → 0.10.6

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 (120) hide show
  1. package/README.md +13 -11
  2. package/cordis.patch.yml +1 -1
  3. package/lib/client.js +10698 -12424
  4. package/lib/client.js.map +7 -0
  5. package/lib/config-values.js +55 -0
  6. package/lib/config-values.js.map +1 -0
  7. package/lib/host/analyze.js +795 -0
  8. package/lib/host/analyze.js.map +1 -0
  9. package/lib/host/config.js +81 -0
  10. package/lib/host/config.js.map +1 -0
  11. package/lib/host/constants.js +25 -0
  12. package/lib/host/constants.js.map +1 -0
  13. package/lib/host/context.js +237 -0
  14. package/lib/host/context.js.map +1 -0
  15. package/lib/host/design-skill.js +42 -0
  16. package/lib/host/design-skill.js.map +1 -0
  17. package/lib/host/effort.js +239 -0
  18. package/lib/host/effort.js.map +1 -0
  19. package/lib/host/filters.js +132 -0
  20. package/lib/host/filters.js.map +1 -0
  21. package/lib/host/history-store.js +404 -0
  22. package/lib/host/history-store.js.map +1 -0
  23. package/lib/host/http.js +46 -0
  24. package/lib/host/http.js.map +1 -0
  25. package/lib/host/messages.js +14 -0
  26. package/lib/host/messages.js.map +1 -0
  27. package/lib/host/mic.js +246 -0
  28. package/lib/host/mic.js.map +1 -0
  29. package/lib/host/models.js +246 -0
  30. package/lib/host/models.js.map +1 -0
  31. package/lib/host/plugin.js +41 -0
  32. package/lib/host/plugin.js.map +1 -0
  33. package/lib/host/promote.js +219 -0
  34. package/lib/host/promote.js.map +1 -0
  35. package/lib/host/routes.js +237 -0
  36. package/lib/host/routes.js.map +1 -0
  37. package/lib/host/sessions.js +181 -0
  38. package/lib/host/sessions.js.map +1 -0
  39. package/lib/host/settings-store.js +140 -0
  40. package/lib/host/settings-store.js.map +1 -0
  41. package/lib/host/speech.js +320 -0
  42. package/lib/host/speech.js.map +1 -0
  43. package/lib/host/tools.js +179 -0
  44. package/lib/host/tools.js.map +1 -0
  45. package/lib/host/types.js +2 -0
  46. package/lib/host/types.js.map +1 -0
  47. package/lib/index.js +10 -3765
  48. package/lib/index.js.map +1 -1
  49. package/lib/prompts.js +1 -1
  50. package/lib/settings.js +17 -27
  51. package/lib/settings.js.map +1 -1
  52. package/lib/types/config-values.d.ts +175 -0
  53. package/lib/types/host/analyze.d.ts +10 -0
  54. package/lib/types/host/config.d.ts +147 -0
  55. package/lib/types/host/constants.d.ts +24 -0
  56. package/lib/types/host/context.d.ts +41 -0
  57. package/lib/types/host/design-skill.d.ts +1 -0
  58. package/lib/types/host/effort.d.ts +61 -0
  59. package/lib/types/host/filters.d.ts +20 -0
  60. package/lib/types/host/history-store.d.ts +13 -0
  61. package/lib/types/host/http.d.ts +8 -0
  62. package/lib/types/host/messages.d.ts +13 -0
  63. package/lib/types/host/mic.d.ts +17 -0
  64. package/lib/types/host/models.d.ts +57 -0
  65. package/lib/types/host/plugin.d.ts +3 -0
  66. package/lib/types/host/promote.d.ts +9 -0
  67. package/lib/types/host/routes.d.ts +14 -0
  68. package/lib/types/host/sessions.d.ts +14 -0
  69. package/lib/types/host/settings-store.d.ts +8 -0
  70. package/lib/types/host/speech.d.ts +23 -0
  71. package/lib/types/host/tools.d.ts +39 -0
  72. package/lib/types/host/types.d.ts +89 -0
  73. package/lib/types/index.d.ts +12 -441
  74. package/lib/types/prompts.d.ts +1 -1
  75. package/lib/types/settings.d.ts +1 -1
  76. package/package.json +31 -21
  77. package/assets/code.png +0 -0
  78. package/assets/detail.png +0 -0
  79. package/assets/quotes.png +0 -0
  80. package/assets/stage1.png +0 -0
  81. package/assets/stage2.png +0 -0
  82. package/assets/translation.png +0 -0
  83. package/assets/voice-card-light.png +0 -0
  84. package/assets/voice-inserted.png +0 -0
  85. package/assets/voice-settings.png +0 -0
  86. package/assets/voice.png +0 -0
  87. package/assets/web.png +0 -0
  88. package/scripts/build-demo-pages.mjs +0 -350
  89. package/scripts/build-vcard-lab.mjs +0 -834
  90. package/scripts/build.sh +0 -147
  91. package/scripts/crop-png.mjs +0 -142
  92. package/scripts/dump-prompt.mjs +0 -143
  93. package/scripts/fixtures/fake-mic-probe.sh +0 -9
  94. package/scripts/gen-star-path.mjs +0 -71
  95. package/scripts/measure-cost.mjs +0 -251
  96. package/scripts/smoke-bridge.mjs +0 -365
  97. package/scripts/smoke-panel.mjs +0 -1903
  98. package/scripts/smoke-voice.mjs +0 -774
  99. package/scripts/test-bridge.mjs +0 -412
  100. package/scripts/test-client.mjs +0 -6175
  101. package/scripts/test-effort-map.mjs +0 -129
  102. package/scripts/test-effort-retry.mjs +0 -109
  103. package/scripts/test-escape-focus.mjs +0 -101
  104. package/scripts/test-filters.mjs +0 -146
  105. package/scripts/test-format.mjs +0 -93
  106. package/scripts/test-guard.mjs +0 -48
  107. package/scripts/test-mic-build.mjs +0 -146
  108. package/scripts/test-mic-idle.mjs +0 -158
  109. package/scripts/test-model-follow.mjs +0 -193
  110. package/scripts/test-prompt.mjs +0 -193
  111. package/scripts/test-route-cache.mjs +0 -138
  112. package/scripts/test-settings-behavior.mjs +0 -188
  113. package/scripts/test-settings-host.mjs +0 -48
  114. package/scripts/test-settings.mjs +0 -329
  115. package/scripts/test-speech.mjs +0 -220
  116. package/scripts/test-transcript.mjs +0 -151
  117. package/src/client/index.js +0 -12580
  118. package/src/index.ts +0 -4254
  119. package/src/prompts.ts +0 -144
  120. package/src/settings.ts +0 -313
package/scripts/build.sh DELETED
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env bash
2
- # dsh-selection-explain 构建脚本
3
- # ① 探测 DSH 运行时(含 @deepseek-ai/* 包的那层 node_modules)
4
- # ② 建立 node_modules 链接(类型解析 + 运行时 import 都依赖它)
5
- # ③ tsc 编译 host 半:src/index.ts → lib/index.js(+ .d.ts)
6
- # ④ 校验并拷贝 client 半:src/client/index.js → lib/client.js
7
- # (客户端是手写的 ModuleLoader bundle,无需打包器)
8
- # 用法:bash scripts/build.sh
9
- set -euo pipefail
10
-
11
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
- cd "$ROOT"
13
-
14
- # ── ① 运行时根:DSH_CHECKOUT → dsh CLI 所在安装 → 常见 checkout → npx 缓存 ──
15
- # 返回「含 @deepseek-ai/dsh-llm 的那层 node_modules」。
16
- probe() {
17
- local base="${1:-}"
18
- [ -n "$base" ] || return 1
19
- # 同时要 dsh-llm 和 cordis:过期链接可能只剩一个 dsh-llm(实测踩过——tsc 报
20
- # "Cannot find module '@deepseek-ai/cordis'",构建失败还会**静默跳过 client 拷贝**,测试于是拿旧 lib 跑出假结果)
21
- if [ -d "$base/@deepseek-ai/dsh-llm" ] && [ -d "$base/@deepseek-ai/cordis" ]; then echo "$base"; return 0; fi
22
- if [ -d "$base/node_modules/@deepseek-ai/dsh-llm" ] && [ -d "$base/node_modules/@deepseek-ai/cordis" ]; then echo "$base/node_modules"; return 0; fi
23
- return 1
24
- }
25
-
26
- find_runtime() {
27
- local found=""
28
- # 0) 项目自己的 node_modules —— CI(GitHub Actions)走这条路:
29
- # npm install 会把 peerDependencies(@deepseek-ai/dsh-llm 等)装进来,
30
- # 没有这一步的话干净环境必然探测失败。
31
- found="$(probe "$ROOT/node_modules" || true)"
32
- if [ -n "$found" ]; then echo "$found"; return; fi
33
- # 1) 显式指定
34
- if [ -n "${DSH_CHECKOUT:-}" ]; then
35
- found="$(probe "$DSH_CHECKOUT" || true)"
36
- if [ -n "$found" ]; then echo "$found"; return; fi
37
- fi
38
- # 2) 正在运行的 dsh CLI(PATH 上的 dsh → 真实安装层)
39
- local cli=""
40
- cli="$(command -v dsh || true)"
41
- if [ -n "$cli" ]; then
42
- local real=""
43
- real="$(node -e 'const fs=require("fs");try{console.log(fs.realpathSync(process.argv[1]))}catch(e){}' "$cli")"
44
- if [ -n "$real" ]; then
45
- # …/node_modules/@deepseek-ai/dsh/lib/bin.js → 逐级向上找
46
- local dir
47
- dir="$(dirname "$real")"
48
- while [ "$dir" != "/" ] && [ -n "$dir" ]; do
49
- found="$(probe "$dir" || true)"
50
- if [ -n "$found" ]; then echo "$found"; return; fi
51
- dir="$(dirname "$dir")"
52
- done
53
- fi
54
- fi
55
- # 3) 常见 checkout / 缓存
56
- local candidate
57
- for candidate in "$HOME/.dsh/dsh-harness" "$HOME/dsh-harness" "$HOME/dsh"; do
58
- found="$(probe "$candidate" || true)"
59
- if [ -n "$found" ]; then echo "$found"; return; fi
60
- done
61
- for candidate in "$HOME"/.npm/_npx/*/node_modules "$HOME"/.npx/*/node_modules; do
62
- found="$(probe "$candidate" || true)"
63
- if [ -n "$found" ]; then echo "$found"; return; fi
64
- done
65
- echo ""
66
- }
67
-
68
- RUNTIME="$(find_runtime)"
69
- if [ -z "$RUNTIME" ]; then
70
- echo "build: 找不到 DSH 运行时(设置 DSH_CHECKOUT 指向含 @deepseek-ai/dsh-llm 的目录)" >&2
71
- exit 1
72
- fi
73
- echo "=== DSH 运行时:$RUNTIME ==="
74
-
75
- # ── ② 链接 @deepseek-ai / @types(整层 scope 链接,类型与运行时共用) ──
76
- # 两种情况必须区分:
77
- # · 本机开发:node_modules 里没有这些包,链接到 DSH 运行时(原来唯一的行为)
78
- # · CI(npm install 过):peerDependencies / devDependencies 已经装成**真实目录**,
79
- # 此时绝不能动它 —— 否则会把 node_modules/@deepseek-ai 链成它自己(自引用死循环,
80
- # probe 随后报 "Too many levels of symbolic links",node_modules 被破坏)。
81
- node -e '
82
- const fs = require("fs")
83
- const path = require("path")
84
- const runtime = process.argv[1]
85
- const root = process.argv[2]
86
- const real = (p) => { try { return fs.realpathSync(p) } catch { return "" } }
87
- for (const name of ["@deepseek-ai", "@types"]) {
88
- const target = path.join(runtime, name)
89
- if (!fs.existsSync(target)) continue
90
- const link = path.join(root, "node_modules", name)
91
- const linkReal = real(link)
92
- // 已是真实目录(非符号链接)→ 保留,npm 装的才是权威版本
93
- if (linkReal && !fs.lstatSync(link).isSymbolicLink()) {
94
- console.log("kept node_modules/" + name + "(真实目录,不链接)")
95
- continue
96
- }
97
- // 已经指向同一个地方 → 无需重建
98
- if (linkReal && linkReal === real(target)) {
99
- console.log("ok node_modules/" + name + "(已指向运行时)")
100
- continue
101
- }
102
- fs.rmSync(link, { recursive: true, force: true })
103
- fs.mkdirSync(path.dirname(link), { recursive: true })
104
- fs.symlinkSync(target, link, process.platform === "win32" ? "junction" : "dir")
105
- console.log("linked node_modules/" + name + " → " + target)
106
- }
107
- ' "$RUNTIME" "$ROOT"
108
-
109
- # ── ③ 编译 host 半 ──
110
- # tsc 查找顺序:项目自己的 node_modules/.bin(CI 走这条,devDependencies 里已声明)
111
- # → PATH 上的全局 tsc → DSH 运行时里的 tsc。
112
- TSC=""
113
- if [ -x "$ROOT/node_modules/.bin/tsc" ]; then TSC="$ROOT/node_modules/.bin/tsc"; fi
114
- if [ -z "$TSC" ]; then TSC="$(command -v tsc || true)"; fi
115
- if [ -z "$TSC" ] && [ -x "$RUNTIME/.bin/tsc" ]; then TSC="$RUNTIME/.bin/tsc"; fi
116
- if [ -z "$TSC" ]; then
117
- echo "build: 找不到 tsc —— 在项目里装一个(npm i -D typescript)或设置 DSH_CHECKOUT" >&2
118
- exit 1
119
- fi
120
- echo "=== tsc $("$TSC" -v) ==="
121
- "$TSC" -p tsconfig.json
122
-
123
- # ── ④ client 半 ──
124
- echo "=== client bundle:src/client/index.js → lib/client.js ==="
125
- node --check src/client/index.js
126
- mkdir -p lib
127
- cp src/client/index.js lib/client.js
128
-
129
- # ── ⑤ 原生探针(macOS 专用)──
130
- # 「检测输入法正在语音输入」靠读 CoreAudio 的进程对象列表(macOS 14.4+),
131
- # Node 没有 FFI,所以编一个小二进制放进 lib/native/,host 需要时 spawn 它
132
- # (见 src/index.ts 里的 micPresence)。
133
- # 编不出来**不算构建失败**:功能降级成 available:false(和"没装识别服务"一个待遇),
134
- # 其余功能一律不受影响;非 macOS 直接跳过。
135
- mkdir -p lib/native
136
- if [ "$(uname -s)" = "Darwin" ] && command -v clang >/dev/null 2>&1; then
137
- if clang -O2 -framework CoreAudio -framework CoreFoundation native/mic-probe.c -o lib/native/mic-probe; then
138
- echo "=== 原生探针:lib/native/mic-probe ==="
139
- else
140
- echo "build: 原生探针编译失败 —— 麦克风占用检测将不可用(不影响其它功能)" >&2
141
- fi
142
- else
143
- echo "build: 跳过原生探针(非 macOS 或没有 clang)—— 麦克风占用检测不可用"
144
- fi
145
-
146
- echo "=== 构建完成 ==="
147
- ls -l lib
@@ -1,142 +0,0 @@
1
- /**
2
- * 裁掉截图四周的空白(仅用 Node 内置 zlib 解码/编码 PNG,不依赖 imagemagick)。
3
- *
4
- * 用途:README 的演示图需要紧凑。Chrome `--screenshot` 只能给固定窗口尺寸,
5
- * 面板比窗口矮时底部会留下大片背景色 —— 这里把纯背景的边缘裁掉。
6
- *
7
- * 用法:node scripts/crop-png.mjs <in.png> <out.png> [边距px]
8
- */
9
- import { readFileSync, writeFileSync } from 'node:fs'
10
- import { inflateSync, deflateSync } from 'node:zlib'
11
-
12
- const [, , inPath, outPath, padArg, tolArg] = process.argv
13
- if (!inPath || !outPath) {
14
- console.error('用法: node scripts/crop-png.mjs <in.png> <out.png> [边距px] [背景容差]')
15
- process.exit(1)
16
- }
17
- const PAD = Number(padArg ?? 0)
18
- // 容差要能吃掉面板投影造成的渐变(实测同一张图背景在 247~255 之间浮动)
19
- const TOL = Number(tolArg ?? 8)
20
-
21
- // ── 读 PNG(假定 Chrome 输出的 8bit RGB/RGBA 非隔行)──
22
- const buf = readFileSync(inPath)
23
- if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('不是 PNG')
24
- let pos = 8
25
- let width = 0
26
- let height = 0
27
- let bitDepth = 0
28
- let colorType = 0
29
- const idat = []
30
- while (pos < buf.length) {
31
- const len = buf.readUInt32BE(pos)
32
- const type = buf.toString('ascii', pos + 4, pos + 8)
33
- const data = buf.subarray(pos + 8, pos + 8 + len)
34
- if (type === 'IHDR') {
35
- width = data.readUInt32BE(0)
36
- height = data.readUInt32BE(4)
37
- bitDepth = data[8]
38
- colorType = data[9]
39
- if (data[12] !== 0) throw new Error('不支持隔行 PNG')
40
- } else if (type === 'IDAT') idat.push(data)
41
- else if (type === 'IEND') break
42
- pos += 12 + len
43
- }
44
- if (bitDepth !== 8 || (colorType !== 2 && colorType !== 6)) {
45
- throw new Error(`不支持的 PNG 格式:bitDepth=${bitDepth} colorType=${colorType}`)
46
- }
47
- const bpp = colorType === 6 ? 4 : 3
48
- const raw = inflateSync(Buffer.concat(idat))
49
-
50
- // ── 反滤波 ──
51
- const stride = width * bpp
52
- const img = Buffer.alloc(height * stride)
53
- for (let y = 0; y < height; y++) {
54
- const filter = raw[y * (stride + 1)]
55
- const line = raw.subarray(y * (stride + 1) + 1, y * (stride + 1) + 1 + stride)
56
- const prev = y > 0 ? img.subarray((y - 1) * stride, y * stride) : null
57
- const cur = img.subarray(y * stride, (y + 1) * stride)
58
- for (let x = 0; x < stride; x++) {
59
- const a = x >= bpp ? cur[x - bpp] : 0
60
- const b = prev ? prev[x] : 0
61
- const c = prev && x >= bpp ? prev[x - bpp] : 0
62
- let v = line[x]
63
- if (filter === 1) v += a
64
- else if (filter === 2) v += b
65
- else if (filter === 3) v += (a + b) >> 1
66
- else if (filter === 4) {
67
- const p = a + b - c
68
- const pa = Math.abs(p - a)
69
- const pb = Math.abs(p - b)
70
- const pc = Math.abs(p - c)
71
- v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c
72
- }
73
- cur[x] = v & 0xff
74
- }
75
- }
76
-
77
- // ── 找内容边界:以左上角像素为背景色 ──
78
- const bg = [img[0], img[1], img[2]]
79
- const isBg = (x, y) => {
80
- const o = y * stride + x * bpp
81
- return (
82
- Math.abs(img[o] - bg[0]) <= TOL && Math.abs(img[o + 1] - bg[1]) <= TOL && Math.abs(img[o + 2] - bg[2]) <= TOL
83
- )
84
- }
85
- let top = 0
86
- let bottom = height - 1
87
- let left = 0
88
- let right = width - 1
89
- outer: for (; top < height; top++) for (let x = 0; x < width; x++) if (!isBg(x, top)) break outer
90
- outer2: for (; bottom > top; bottom--) for (let x = 0; x < width; x++) if (!isBg(x, bottom)) break outer2
91
- outer3: for (; left < width; left++) for (let y = top; y <= bottom; y++) if (!isBg(left, y)) break outer3
92
- outer4: for (; right > left; right--) for (let y = top; y <= bottom; y++) if (!isBg(right, y)) break outer4
93
-
94
- top = Math.max(0, top - PAD)
95
- left = Math.max(0, left - PAD)
96
- bottom = Math.min(height - 1, bottom + PAD)
97
- right = Math.min(width - 1, right + PAD)
98
- const w = right - left + 1
99
- const h = bottom - top + 1
100
-
101
- // ── 重新编码(每行用 filter 0)──
102
- const outStride = w * bpp
103
- const rawOut = Buffer.alloc(h * (outStride + 1))
104
- for (let y = 0; y < h; y++) {
105
- rawOut[y * (outStride + 1)] = 0
106
- img.copy(rawOut, y * (outStride + 1) + 1, (top + y) * stride + left * bpp, (top + y) * stride + left * bpp + outStride)
107
- }
108
- const chunk = (type, data) => {
109
- const len = Buffer.alloc(4)
110
- len.writeUInt32BE(data.length)
111
- const body = Buffer.concat([Buffer.from(type, 'ascii'), data])
112
- const crc = Buffer.alloc(4)
113
- crc.writeUInt32BE(crc32(body) >>> 0)
114
- return Buffer.concat([len, body, crc])
115
- }
116
- let table = null
117
- function crc32(b) {
118
- if (!table) {
119
- table = new Int32Array(256)
120
- for (let n = 0; n < 256; n++) {
121
- let c = n
122
- for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
123
- table[n] = c
124
- }
125
- }
126
- let c = 0xffffffff
127
- for (let i = 0; i < b.length; i++) c = table[(c ^ b[i]) & 0xff] ^ (c >>> 8)
128
- return c ^ 0xffffffff
129
- }
130
- const ihdr = Buffer.alloc(13)
131
- ihdr.writeUInt32BE(w, 0)
132
- ihdr.writeUInt32BE(h, 4)
133
- ihdr[8] = 8
134
- ihdr[9] = colorType
135
- const png = Buffer.concat([
136
- Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
137
- chunk('IHDR', ihdr),
138
- chunk('IDAT', deflateSync(rawOut, { level: 9 })),
139
- chunk('IEND', Buffer.alloc(0)),
140
- ])
141
- writeFileSync(outPath, png)
142
- console.log(`✓ ${outPath} ${width}x${height} → ${w}x${h}`)
@@ -1,143 +0,0 @@
1
- /**
2
- * 打印一次划词解读实际发给模型的提示词(system + user)与各段尺寸。
3
- *
4
- * 用法:
5
- * node scripts/dump-prompt.mjs <sessionId> "<选中文字>" ["<上下文片段>"]
6
- * node scripts/dump-prompt.mjs --no-session "<选中文字>" # 不带会话背景
7
- * node scripts/dump-prompt.mjs --all # 一次打出五种模式的系统提示词
8
- * node scripts/dump-prompt.mjs --no-session "x" --stage=detail --kind=code --question="为什么?"
9
- * SEL_ORIGIN=http://127.0.0.1:3080 node scripts/dump-prompt.mjs ...
10
- *
11
- * 原理:host 支持诊断开关 debug:true —— SSE 的 start 事件里会带上 system / user 原文。
12
- */
13
- const args = process.argv.slice(2)
14
- const ORIGIN = process.env.SEL_ORIGIN || 'http://127.0.0.1:3080'
15
-
16
- const noSession = args[0] === '--no-session'
17
- const sessionId = noSession ? '' : args[0] || ''
18
- const text = (noSession ? args[1] : args[1]) || 'the migration ran long'
19
- const context =
20
- (noSession ? args[2] : args[2]) || 'PM:这周能发布吗?\nDev:【the migration ran long】,得顺延到周三。\nPM:那就周三,先冻结功能。'
21
-
22
- const FULL = args.includes('--full') // --full:等完整回答,附带打印 token 用量与缓存命中
23
- const STAGE = (args.find((a) => a.startsWith('--stage=')) || '').slice('--stage='.length)
24
- const KIND = (args.find((a) => a.startsWith('--kind=')) || '').slice('--kind='.length)
25
- const QUESTION = (args.find((a) => a.startsWith('--question=')) || '').slice('--question='.length)
26
- const WEB = args.includes('--web') // --web:按"网页模式"发(host 会注入设计规范)
27
-
28
- /** 发一次请求,只取 start 事件里的 system / user(debug:true 才有)。 */
29
- async function fetchPrompt(payload) {
30
- const response = await fetch(`${ORIGIN}/selection-explain/api/analyze`, {
31
- method: 'POST',
32
- headers: { 'content-type': 'application/json' },
33
- body: JSON.stringify({ label: '对话消息', ...(sessionId ? { sessionId } : {}), debug: true, ...payload }),
34
- })
35
- const reader = response.body.getReader()
36
- const decoder = new TextDecoder()
37
- let buffer = ''
38
- for (;;) {
39
- const { done, value } = await reader.read()
40
- if (done) break
41
- buffer += decoder.decode(value, { stream: true })
42
- let index
43
- while ((index = buffer.indexOf('\n\n')) >= 0) {
44
- const block = buffer.slice(0, index)
45
- buffer = buffer.slice(index + 2)
46
- const line = block.trim()
47
- if (!line.startsWith('data:')) continue
48
- let message
49
- try {
50
- message = JSON.parse(line.slice(5).trim())
51
- } catch {
52
- continue
53
- }
54
- if (message.type === 'start') {
55
- // 关掉流:我们只要提示词,不要它生成完(省时省钱)
56
- try {
57
- await reader.cancel()
58
- } catch (error) {
59
- /* noop */
60
- }
61
- return message
62
- }
63
- }
64
- }
65
- return null
66
- }
67
-
68
- // --all:把四种模式(翻译/解读、代码注释、详解、追问)的系统提示词一次性打出来
69
- if (args.includes('--all')) {
70
- const modes = [
71
- { title: '① 翻译(选中含英文)', payload: { text: 'the migration ran long', context, stage: 'translation' } },
72
- { title: '② 解读(选中纯中文)', payload: { text: '先对齐一下,我们再闭环', context: '评审会上他说"先对齐一下,我们再闭环"。', stage: 'translation' } },
73
- { title: '③ 注释(选中代码)', payload: { text: 'const a = 1\nfunction f() { return a }', context: '代码片段。', kind: 'code', stage: 'translation' } },
74
- { title: '④ 详解(点展开详解)', payload: { text: 'the migration ran long', context, stage: 'detail' } },
75
- { title: '⑤ 追问(小窗对话)', payload: { text: 'the migration ran long', context, question: '为什么会这样?', history: [{ role: 'assistant', text: '## 翻译\n迁移跑久了。' }] } },
76
- { title: '⑥ 追问 + 网页模式(注入 skills/web-design/SKILL.md)', payload: { text: 'the migration ran long', context, question: '为什么?给我一页对比', webAnswer: true, history: [{ role: 'assistant', text: '## 翻译\n迁移跑久了。' }] } },
77
- ]
78
- for (const mode of modes) {
79
- const start = await fetchPrompt(mode.payload)
80
- console.log(`\n${'='.repeat(78)}\n### ${mode.title}\n${'='.repeat(78)}`)
81
- if (!start) {
82
- console.log('(没拿到 start 事件)')
83
- continue
84
- }
85
- console.log(`[system ${start.debug?.sizes?.system ?? '?'} 字 / user ${start.debug?.sizes?.user ?? '?'} 字] tools=${JSON.stringify(start.tools || [])} effort=${start.effort}`)
86
- console.log('\n----- SYSTEM -----\n' + (start.debug?.system || '(未返回)'))
87
- console.log('\n----- USER(第一段的开头)-----\n' + String(start.debug?.user || '').slice(0, 600))
88
- }
89
- process.exit(0)
90
- }
91
-
92
- const response = await fetch(`${ORIGIN}/selection-explain/api/analyze`, {
93
- method: 'POST',
94
- headers: { 'content-type': 'application/json' },
95
- body: JSON.stringify({
96
- text,
97
- context,
98
- label: '对话消息',
99
- ...(sessionId ? { sessionId } : {}),
100
- ...(STAGE ? { stage: STAGE } : {}),
101
- ...(KIND ? { kind: KIND } : {}),
102
- ...(QUESTION ? { question: QUESTION, history: [{ role: 'assistant', text: '(上一轮)' }] } : {}),
103
- ...(WEB ? { webAnswer: true } : {}),
104
- debug: true,
105
- }),
106
- })
107
-
108
- const reader = response.body.getReader()
109
- const decoder = new TextDecoder()
110
- let buffer = ''
111
- let printed = false
112
- while (!printed) {
113
- const { done, value } = await reader.read()
114
- if (done) break
115
- buffer += decoder.decode(value, { stream: true })
116
- let index
117
- while ((index = buffer.indexOf('\n\n')) >= 0) {
118
- const block = buffer.slice(0, index)
119
- buffer = buffer.slice(index + 2)
120
- const line = block.trim()
121
- if (!line.startsWith('data:')) continue
122
- let message
123
- try {
124
- message = JSON.parse(line.slice(5).trim())
125
- } catch {
126
- continue
127
- }
128
- if (message.type !== 'start') continue
129
- const sizes = message.debug?.sizes || {}
130
- console.log('=== 尺寸(字符)===')
131
- console.log(`system ${sizes.system} | user ${sizes.user} | 局部片段 ${message.context.chars} | 会话背景 ${message.context.session ? message.context.session.chars : 0}${message.context.session ? '(标记命中=' + message.context.session.marked + ')' : ''}`)
132
- console.log('\n=== SYSTEM ===\n' + (message.debug?.system || '(未返回)'))
133
- console.log('\n=== USER(实际注入的上下文)===\n' + (message.debug?.user || '(未返回)'))
134
- printed = true
135
- if (!FULL) break
136
- }
137
- if (FULL) {
138
- const line = buffer.trim()
139
- void line
140
- }
141
- }
142
- reader.cancel().catch(() => {})
143
- if (!printed) console.error('未取到 start 事件(服务是否在跑?地址:' + ORIGIN + ')')
@@ -1,9 +0,0 @@
1
- #!/bin/sh
2
- # 假探针(宿主生命周期测试用):只记两笔 —— 起来了 / 被杀了。
3
- # 真探针要读 CoreAudio,测试里既没必要也跑不稳;这里要验的是**进程什么时候在**。
4
- echo "start $*" >> "$DSH_SEL_FAKE_PROBE_LOG"
5
- echo '{"capturing":false,"processes":[]}'
6
- trap 'echo stop >> "$DSH_SEL_FAKE_PROBE_LOG"; exit 0' TERM
7
- # 注意:POSIX sh 里 trap 要等**当前前台命令**跑完才执行,所以这个等待必须短 ——
8
- # 用 sleep 1 的话,SIGTERM 之后最多要 1 秒才写得出 stop,测试会误判成"没停"。
9
- while true; do sleep 0.1; done
@@ -1,71 +0,0 @@
1
- /**
2
- * 生成"圆点 ⇄ 星芒"的两条 SVG path(写进 src/client/index.js 的 PILL_STAR_DOT_D / PILL_STAR_D)。
3
- *
4
- * 为什么是这两条:
5
- * 「✦ 解读」浮标里那颗星是 16 画布上的八边形 —— 外半径 5.6、内半径 2.1213(内外比 .3788),四条边是**直线**
6
- * (path: M8 2.4l1.5 4.1 4.1 1.5-4.1 1.5L8 13.6 6.5 9.5 2.4 8l4.1-1.5L8 2.4z)。
7
- * 球里按 8.4/5.6 = 1.5 倍放大(墨迹 16.8px),并把"圆"和"星"都看成 32 边形逐顶点插值:
8
- * · 32 个顶点按 11.25° 取样;星那条边上的取样点由射线与多边形求交得到,精确落在线段上
9
- * · p=0:32 个顶点同半径 4 → 8px 圆点(8px 下与正圆无差,最大偏差 0.02px)
10
- * · p=1:32 个顶点正好是星边界(尖与谷仍是精确顶点)→ 与按钮那条 path 逐点相同(仅放大 1.5 倍)
11
- * 两条 path 命令结构一致(M + 31×L + Z),所以 Chrome 能对 d 做插值。
12
- *
13
- * 用法:node scripts/gen-star-path.mjs
14
- */
15
- var TIP = 6.0 // 墨迹 12.0px = 胶囊里文字的字号(用户要求:和文字一样高)
16
- var VALLEY = TIP * 0.3788
17
- var SAMPLES = 32
18
-
19
- function polar(angDeg, r) {
20
- var a = (angDeg * Math.PI) / 180
21
- return { x: r * Math.cos(a), y: r * Math.sin(a) }
22
- }
23
- function starPolygon() {
24
- var pts = []
25
- for (var k = 0; k < 4; k += 1) {
26
- pts.push(polar(k * 90, TIP))
27
- pts.push(polar(k * 90 + 45, VALLEY))
28
- }
29
- return pts
30
- }
31
- function cross(ax, ay, bx, by) { return ax * by - ay * bx }
32
- function boundaryRadius(poly, angDeg) {
33
- var d = polar(angDeg, 1)
34
- var best = Infinity
35
- for (var i = 0; i < poly.length; i += 1) {
36
- var p0 = poly[i]
37
- var p1 = poly[(i + 1) % poly.length]
38
- var ex = p1.x - p0.x
39
- var ey = p1.y - p0.y
40
- var den = cross(d.x, d.y, ex, ey)
41
- if (Math.abs(den) < 1e-9) continue
42
- var t = cross(p0.x, p0.y, ex, ey) / den
43
- var u = cross(p0.x, p0.y, d.x, d.y) / den
44
- if (t > 0 && u >= -1e-6 && u <= 1 + 1e-6 && t < best) best = t
45
- }
46
- return best === Infinity ? 4 : best
47
- }
48
- function pathFor(radiusOf) {
49
- var poly = starPolygon()
50
- var out = []
51
- for (var i = 0; i < SAMPLES; i += 1) {
52
- var ang = (i * 360) / SAMPLES
53
- var r = radiusOf(boundaryRadius(poly, ang))
54
- var pt = polar(ang, r)
55
- out.push((i ? 'L' : 'M') + round(pt.x) + ' ' + round(pt.y))
56
- }
57
- return out.join('') + 'Z'
58
- }
59
- function round(v) { return String(Math.round(v * 100) / 100) }
60
-
61
- var DOT = pathFor(function () { return 4 })
62
- var STAR = pathFor(function (r) { return r })
63
-
64
- console.log('// p=0(8px 圆点):')
65
- console.log("var PILL_STAR_DOT_D = '" + DOT + "'")
66
- console.log('')
67
- console.log('// p=1(按钮那颗星,放大 1.5 倍):')
68
- console.log("var PILL_STAR_D = '" + STAR + "'")
69
- console.log('')
70
- console.log('// 长度: DOT ' + DOT.length + ' 字符, STAR ' + STAR.length + ' 字符')
71
- console.log('// 命令结构一致(可插值): ' + (DOT.split(/[MLZ]/).length === STAR.split(/[MLZ]/).length))