@yfwu2020/dsh-selection-explain 0.10.3 → 0.10.5
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/README.md +13 -11
- package/cordis.patch.yml +1 -1
- package/lib/client.js +10694 -12424
- package/lib/client.js.map +7 -0
- package/lib/config-values.js +55 -0
- package/lib/config-values.js.map +1 -0
- package/lib/host/analyze.js +795 -0
- package/lib/host/analyze.js.map +1 -0
- package/lib/host/config.js +81 -0
- package/lib/host/config.js.map +1 -0
- package/lib/host/constants.js +25 -0
- package/lib/host/constants.js.map +1 -0
- package/lib/host/context.js +237 -0
- package/lib/host/context.js.map +1 -0
- package/lib/host/design-skill.js +42 -0
- package/lib/host/design-skill.js.map +1 -0
- package/lib/host/effort.js +239 -0
- package/lib/host/effort.js.map +1 -0
- package/lib/host/filters.js +132 -0
- package/lib/host/filters.js.map +1 -0
- package/lib/host/history-store.js +404 -0
- package/lib/host/history-store.js.map +1 -0
- package/lib/host/http.js +46 -0
- package/lib/host/http.js.map +1 -0
- package/lib/host/messages.js +14 -0
- package/lib/host/messages.js.map +1 -0
- package/lib/host/mic.js +246 -0
- package/lib/host/mic.js.map +1 -0
- package/lib/host/models.js +246 -0
- package/lib/host/models.js.map +1 -0
- package/lib/host/plugin.js +41 -0
- package/lib/host/plugin.js.map +1 -0
- package/lib/host/promote.js +219 -0
- package/lib/host/promote.js.map +1 -0
- package/lib/host/routes.js +237 -0
- package/lib/host/routes.js.map +1 -0
- package/lib/host/sessions.js +181 -0
- package/lib/host/sessions.js.map +1 -0
- package/lib/host/settings-store.js +140 -0
- package/lib/host/settings-store.js.map +1 -0
- package/lib/host/speech.js +320 -0
- package/lib/host/speech.js.map +1 -0
- package/lib/host/tools.js +179 -0
- package/lib/host/tools.js.map +1 -0
- package/lib/host/types.js +2 -0
- package/lib/host/types.js.map +1 -0
- package/lib/index.js +10 -3691
- package/lib/index.js.map +1 -1
- package/lib/prompts.js +1 -1
- package/lib/settings.js +17 -27
- package/lib/settings.js.map +1 -1
- package/lib/types/config-values.d.ts +175 -0
- package/lib/types/host/analyze.d.ts +10 -0
- package/lib/types/host/config.d.ts +147 -0
- package/lib/types/host/constants.d.ts +24 -0
- package/lib/types/host/context.d.ts +41 -0
- package/lib/types/host/design-skill.d.ts +1 -0
- package/lib/types/host/effort.d.ts +61 -0
- package/lib/types/host/filters.d.ts +20 -0
- package/lib/types/host/history-store.d.ts +13 -0
- package/lib/types/host/http.d.ts +8 -0
- package/lib/types/host/messages.d.ts +13 -0
- package/lib/types/host/mic.d.ts +17 -0
- package/lib/types/host/models.d.ts +57 -0
- package/lib/types/host/plugin.d.ts +3 -0
- package/lib/types/host/promote.d.ts +9 -0
- package/lib/types/host/routes.d.ts +14 -0
- package/lib/types/host/sessions.d.ts +14 -0
- package/lib/types/host/settings-store.d.ts +8 -0
- package/lib/types/host/speech.d.ts +23 -0
- package/lib/types/host/tools.d.ts +39 -0
- package/lib/types/host/types.d.ts +89 -0
- package/lib/types/index.d.ts +12 -441
- package/lib/types/prompts.d.ts +1 -1
- package/lib/types/settings.d.ts +1 -1
- package/package.json +31 -21
- package/assets/code.png +0 -0
- package/assets/detail.png +0 -0
- package/assets/quotes.png +0 -0
- package/assets/stage1.png +0 -0
- package/assets/stage2.png +0 -0
- package/assets/translation.png +0 -0
- package/assets/voice-card-light.png +0 -0
- package/assets/voice-inserted.png +0 -0
- package/assets/voice-settings.png +0 -0
- package/assets/voice.png +0 -0
- package/assets/web.png +0 -0
- package/scripts/build-demo-pages.mjs +0 -350
- package/scripts/build-vcard-lab.mjs +0 -834
- package/scripts/build.sh +0 -147
- package/scripts/crop-png.mjs +0 -142
- package/scripts/dump-prompt.mjs +0 -143
- package/scripts/fixtures/fake-mic-probe.sh +0 -9
- package/scripts/gen-star-path.mjs +0 -71
- package/scripts/measure-cost.mjs +0 -251
- package/scripts/smoke-bridge.mjs +0 -365
- package/scripts/smoke-panel.mjs +0 -1903
- package/scripts/smoke-voice.mjs +0 -774
- package/scripts/test-bridge.mjs +0 -412
- package/scripts/test-client.mjs +0 -6175
- package/scripts/test-effort-map.mjs +0 -129
- package/scripts/test-effort-retry.mjs +0 -109
- package/scripts/test-escape-focus.mjs +0 -101
- package/scripts/test-filters.mjs +0 -146
- package/scripts/test-format.mjs +0 -93
- package/scripts/test-guard.mjs +0 -48
- package/scripts/test-mic-idle.mjs +0 -158
- package/scripts/test-model-follow.mjs +0 -193
- package/scripts/test-prompt.mjs +0 -193
- package/scripts/test-route-cache.mjs +0 -138
- package/scripts/test-settings-behavior.mjs +0 -188
- package/scripts/test-settings-host.mjs +0 -48
- package/scripts/test-settings.mjs +0 -329
- package/scripts/test-speech.mjs +0 -220
- package/scripts/test-transcript.mjs +0 -151
- package/src/client/index.js +0 -12580
- package/src/index.ts +0 -4179
- package/src/prompts.ts +0 -144
- 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
|
package/scripts/crop-png.mjs
DELETED
|
@@ -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}`)
|
package/scripts/dump-prompt.mjs
DELETED
|
@@ -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))
|