@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.
- package/README.md +13 -11
- package/cordis.patch.yml +1 -1
- package/lib/client.js +10698 -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 -3765
- 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-build.mjs +0 -146
- 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 -4254
- package/src/prompts.ts +0 -144
- package/src/settings.ts +0 -313
package/package.json
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yfwu2020/dsh-selection-explain",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"description": "DSH 划词解读:选中文字浮出按钮,点击弹窗流式显示专业中英翻译 + 该文字在当前上下文中的含义详解",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/types/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"lib",
|
|
10
|
-
"native",
|
|
11
|
-
"src",
|
|
12
|
-
"scripts",
|
|
10
|
+
"native/mic-probe.c",
|
|
13
11
|
"cordis.patch.yml",
|
|
14
12
|
"skills",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
17
15
|
],
|
|
18
16
|
"license": "MIT",
|
|
19
17
|
"repository": {
|
|
@@ -37,24 +35,33 @@
|
|
|
37
35
|
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.3",
|
|
38
36
|
"@deepseek-ai/dsh-llm": "0.1.5-rc.3",
|
|
39
37
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
40
|
-
"@types/node": "
|
|
41
|
-
"typescript": "
|
|
38
|
+
"@types/node": "22.14.0",
|
|
39
|
+
"typescript": "5.9.3",
|
|
40
|
+
"esbuild": "0.28.2"
|
|
42
41
|
},
|
|
43
42
|
"scripts": {
|
|
44
43
|
"build": "bash scripts/build.sh",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"test
|
|
51
|
-
"test:
|
|
52
|
-
"test:
|
|
53
|
-
"test:
|
|
54
|
-
"test:
|
|
55
|
-
"test:
|
|
56
|
-
"test:
|
|
57
|
-
"test:
|
|
44
|
+
"prepack": "npm run build",
|
|
45
|
+
"typecheck": "npm run typecheck:host && npm run typecheck:client",
|
|
46
|
+
"typecheck:host": "tsc -p tsconfig.json --noEmit",
|
|
47
|
+
"typecheck:client": "tsc -p tsconfig.client.json",
|
|
48
|
+
"pretest": "npm run build",
|
|
49
|
+
"test": "node scripts/run-tests.mjs all",
|
|
50
|
+
"test:unit": "npm run build && node scripts/run-tests.mjs unit",
|
|
51
|
+
"test:integration": "npm run build && node scripts/run-tests.mjs integration",
|
|
52
|
+
"test:browser": "npm run build && node scripts/run-tests.mjs browser",
|
|
53
|
+
"test:online": "npm run build && node scripts/run-tests.mjs online",
|
|
54
|
+
"test:effort": "npm run build && node tests/unit/effort-retry.test.mjs",
|
|
55
|
+
"test:effort-map": "npm run build && node tests/unit/effort-map.test.mjs",
|
|
56
|
+
"test:transcript": "npm run build && node tests/unit/transcript.test.mjs",
|
|
57
|
+
"test:guard": "npm run build && node tests/unit/guard.test.mjs",
|
|
58
|
+
"test:format": "npm run build && node tests/unit/format.test.mjs",
|
|
59
|
+
"test:bridge": "npm run build && node tests/unit/bridge.test.mjs",
|
|
60
|
+
"test:smoke": "npm run build && node tests/browser/bridge.test.mjs",
|
|
61
|
+
"test:panel": "npm run build && node tests/browser/panel.test.mjs",
|
|
62
|
+
"test:prompt": "npm run build && node tests/unit/prompt.test.mjs",
|
|
63
|
+
"test:speech": "npm run build && node tests/unit/speech.test.mjs",
|
|
64
|
+
"test:voice": "npm run build && node tests/browser/voice.test.mjs"
|
|
58
65
|
},
|
|
59
66
|
"exports": {
|
|
60
67
|
".": {
|
|
@@ -74,5 +81,8 @@
|
|
|
74
81
|
"inject": [],
|
|
75
82
|
"platform": "web"
|
|
76
83
|
}
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22.14.0"
|
|
77
87
|
}
|
|
78
88
|
}
|
package/assets/code.png
DELETED
|
Binary file
|
package/assets/detail.png
DELETED
|
Binary file
|
package/assets/quotes.png
DELETED
|
Binary file
|
package/assets/stage1.png
DELETED
|
Binary file
|
package/assets/stage2.png
DELETED
|
Binary file
|
package/assets/translation.png
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/assets/voice.png
DELETED
|
Binary file
|
package/assets/web.png
DELETED
|
Binary file
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 生成 README 用的小窗演示页(docs/demo/*.html)。
|
|
3
|
-
*
|
|
4
|
-
* 目的:README 里的功能截图必须**和真实面板长得一样**,所以样式不能手抄 ——
|
|
5
|
-
* 这里直接从 lib/client.js 里把模块级 `CSS` 常量抽出来注入演示页,
|
|
6
|
-
* 类名 / 结构 / 文案也照 src/client/index.js 的渲染器与真实输出写。
|
|
7
|
-
*
|
|
8
|
-
* 用法:node scripts/build-demo-pages.mjs
|
|
9
|
-
* 产物:docs/demo/stage1.html / stage2.html / code.html(再交给 Chrome 截图)
|
|
10
|
-
*/
|
|
11
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
12
|
-
import { resolve, dirname } from 'node:path'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
|
|
15
|
-
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
16
|
-
const ROOT = resolve(HERE, '..')
|
|
17
|
-
|
|
18
|
-
/** 从客户端 bundle 里取出模块级 CSS 常量(不手抄,保证与线上一致)。 */
|
|
19
|
-
function extractCss() {
|
|
20
|
-
const src = readFileSync(resolve(ROOT, 'lib', 'client.js'), 'utf8')
|
|
21
|
-
const m = src.match(/var CSS = \[([\s\S]*?)\]\s*\.join\(''\)/) || src.match(/var CSS = \[([\s\S]*?)\]\.join\('\\n'\)/)
|
|
22
|
-
if (!m) throw new Error('未能在 lib/client.js 里找到 CSS 常量')
|
|
23
|
-
// 元素是 '...' 单引号字符串字面量
|
|
24
|
-
const parts = []
|
|
25
|
-
const re = /'((?:[^'\\]|\\.)*)'/g
|
|
26
|
-
let e
|
|
27
|
-
while ((e = re.exec(m[1])) !== null) parts.push(e[1].replace(/\\'/g, "'").replace(/\\n/g, '\n'))
|
|
28
|
-
return parts.join('\n')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** DSH 主题变量:用浅色主题的实际取值,让截图看起来像真的嵌在 DSH 里。 */
|
|
32
|
-
const THEME_VARS = `:root{
|
|
33
|
-
--dsw-alias-bg-layer-1: #ffffff;
|
|
34
|
-
--dsw-alias-label-primary: #1a1a1a;
|
|
35
|
-
--dsw-alias-label-secondary: #6b7280;
|
|
36
|
-
--dsw-alias-label-primary-foreground: #ffffff;
|
|
37
|
-
--dsw-alias-interactive-bg-hover: rgba(140,140,140,.11);
|
|
38
|
-
--dsw-alias-border-l3: rgba(140,140,140,.35);
|
|
39
|
-
--dsw-alias-border-l4: rgba(140,140,140,.22);
|
|
40
|
-
--dsw-alias-button-primary-fill: #3b6ef5;
|
|
41
|
-
--sel-a1: #0d9488;
|
|
42
|
-
--sel-a2: #0f766e;
|
|
43
|
-
}`
|
|
44
|
-
|
|
45
|
-
const PAGE = (title, css, body) => `<!doctype html>
|
|
46
|
-
<html lang="zh-CN"><head><meta charset="utf-8"><title>${title}</title>
|
|
47
|
-
<style>
|
|
48
|
-
/* 只渲染小窗本体:纯白底、无外边距,宽度固定 540 —— 截图后统一裁切 */
|
|
49
|
-
html,body{margin:0;padding:0;background:#ffffff;font-family:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif}
|
|
50
|
-
body{display:block;width:540px}
|
|
51
|
-
${THEME_VARS}
|
|
52
|
-
${css}
|
|
53
|
-
/* 演示页专用:把浮层定位改成静态排版、并显示面板(真实面板默认 display:none,由 JS 打开) */
|
|
54
|
-
.dsh-sel-layer{position:static !important;pointer-events:auto}
|
|
55
|
-
/* 截图只要"小窗本体":去掉投影、边框、圆角 —— 否则裁切后会在上下边缘留下一圈灰线
|
|
56
|
-
(真实面板的投影是给浮层用的,单独截图时只会变成灰边) */
|
|
57
|
-
.dsh-sel-panel{position:static !important;display:flex !important;max-height:none !important;width:540px !important;
|
|
58
|
-
box-shadow:none !important;border:0 !important;border-radius:0 !important}
|
|
59
|
-
/* 首轮:真实行为是「翻译一出来输入框就出现」(askRow.style.display = wide || translationReady),
|
|
60
|
-
所以演示页也照实显示,不再隐藏 */
|
|
61
|
-
.dsh-sel-ask{display:flex !important;border:0 !important}
|
|
62
|
-
</style></head>
|
|
63
|
-
<body>${body}</body></html>
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
const panel = (stage, inner) => `<div class="dsh-sel-layer">
|
|
67
|
-
<div class="dsh-sel-panel" data-stage="${stage}" data-theme="light" role="dialog" aria-label="划词解读">
|
|
68
|
-
<div class="dsh-sel-head">
|
|
69
|
-
<span class="dsh-sel-mark"></span>
|
|
70
|
-
<span class="dsh-sel-title">划词解读</span>
|
|
71
|
-
<span class="dsh-sel-headspace"></span>
|
|
72
|
-
<button class="dsh-sel-action">🕘 最近</button>
|
|
73
|
-
<button class="dsh-sel-action">↗ 升格</button>
|
|
74
|
-
<button class="dsh-sel-icon" title="关闭">✕</button>
|
|
75
|
-
</div>
|
|
76
|
-
<div class="dsh-sel-body">
|
|
77
|
-
${inner}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>`
|
|
81
|
-
|
|
82
|
-
const quote = (text) => ` <div class="dsh-sel-quote">${text}</div>`
|
|
83
|
-
|
|
84
|
-
const section = (key, title, content) => ` <div class="dsh-sel-sec" data-sec="${key}">
|
|
85
|
-
<div class="dsh-sel-sh"><i></i><span>${title}</span><span class="dsh-sel-hint"></span></div>
|
|
86
|
-
<div class="dsh-sel-c">
|
|
87
|
-
${content}
|
|
88
|
-
</div>
|
|
89
|
-
</div>`
|
|
90
|
-
|
|
91
|
-
const p = (html) => ` <p>${html}</p>`
|
|
92
|
-
const list = (level, items) => ` <div class="dsh-sel-list" data-level="${level}">
|
|
93
|
-
${items.map((it) => ` <div class="dsh-sel-item"${it.num ? ' data-num="1"' : ''}><span class="dsh-sel-bullet">${it.bullet || '•'}</span><span>${it.text}</span></div>`).join('\n')}
|
|
94
|
-
</div>`
|
|
95
|
-
const callout = (tag, text) => ` <div class="dsh-sel-callout"><span class="dsh-sel-callout-tag">${tag}</span>${text}</div>`
|
|
96
|
-
|
|
97
|
-
const expand = ` <button class="dsh-sel-expand"><span>↓ 展开详解</span></button>`
|
|
98
|
-
|
|
99
|
-
/** 真实图标(照 src/client/index.js 的 markdownIcon / windowIcon / sendIcon)。 */
|
|
100
|
-
const mdIcon = `<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 17.5V7.5l4.4 5 4.4-5v10"></path><path d="M17.4 7v9.6M14.3 13.6l3.1 3.1 3.1-3.1"></path></svg>`
|
|
101
|
-
const webIcon = `<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="3"></rect><path d="M3 9.6h18"></path></svg>`
|
|
102
|
-
const sendIcon = `<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 13.2V3.4"></path><path d="M3.9 7.5 8 3.4l4.1 4.1"></path></svg>`
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 输入区(结构照 src/client/index.js 的真实构建顺序):
|
|
106
|
-
* .dsh-sel-ask > .dsh-sel-quotes + textarea.dsh-sel-askbox + .dsh-sel-asktools
|
|
107
|
-
* asktools = 输出偏好(文字 + 分段开关 + 滑动药丸) + askspace(撑开) + 模型胶囊 + 发送键
|
|
108
|
-
* quotes = 待发送的引用卡片(空串 = 空态,整块收起)
|
|
109
|
-
*/
|
|
110
|
-
const askRow = (webOn, model = 'deepseek-v4.1-flash', tier = '高', quotes = '') => ` <div class="dsh-sel-ask">
|
|
111
|
-
<div class="dsh-sel-quotes"${quotes ? ' data-show="1"' : ''}>${quotes}</div>
|
|
112
|
-
<textarea class="dsh-sel-askbox" rows="1" placeholder="就这段文字继续追问…(Enter 发送,Shift+Enter 换行)"></textarea>
|
|
113
|
-
<div class="dsh-sel-asktools">
|
|
114
|
-
<span class="dsh-sel-pref" data-on="${webOn ? '1' : '0'}">
|
|
115
|
-
<span class="dsh-sel-preftext">输出偏好</span>
|
|
116
|
-
<span class="dsh-sel-seg" role="radiogroup" aria-label="输出偏好">
|
|
117
|
-
<span class="dsh-sel-segpill"></span>
|
|
118
|
-
<button class="dsh-sel-segcell" role="radio" aria-label="Markdown:普通段落,最省时间"${webOn ? '' : ' data-on="1"'}>${mdIcon}</button>
|
|
119
|
-
<button class="dsh-sel-segcell" role="radio" aria-label="网页:复杂问题生成一个 HTML 页面"${webOn ? ' data-on="1"' : ''}>${webIcon}</button>
|
|
120
|
-
</span>
|
|
121
|
-
</span>
|
|
122
|
-
<span class="dsh-sel-askspace"></span>
|
|
123
|
-
<button class="dsh-sel-picker" type="button" aria-haspopup="menu" aria-expanded="false">
|
|
124
|
-
<span class="dsh-sel-picker-name">${model}</span>
|
|
125
|
-
<span class="dsh-sel-picker-tier">· ${tier}</span>
|
|
126
|
-
<span class="dsh-sel-picker-caret">▼</span>
|
|
127
|
-
</button>
|
|
128
|
-
<button class="dsh-sel-iconbtn dsh-sel-asksend" type="button" aria-label="发送">${sendIcon}</button>
|
|
129
|
-
</div>
|
|
130
|
-
</div>`
|
|
131
|
-
|
|
132
|
-
const chatBubble = (role, html) =>
|
|
133
|
-
` <div class="dsh-sel-bubble ${role === 'user' ? 'dsh-sel-bubble-user' : 'dsh-sel-bubble-bot'}">${html}</div>`
|
|
134
|
-
|
|
135
|
-
// ───────────────────────── 状态一:首轮只出翻译 ─────────────────────────
|
|
136
|
-
const stage1 = panel(
|
|
137
|
-
'translation',
|
|
138
|
-
[
|
|
139
|
-
quote('the migration ran long'),
|
|
140
|
-
section(
|
|
141
|
-
'translation',
|
|
142
|
-
'翻译',
|
|
143
|
-
[
|
|
144
|
-
p('这句话里需要翻译的是 <b>the migration ran long</b>:'),
|
|
145
|
-
list(0, [{ text: '<b>migration</b> /maɪˈɡreɪʃn/ —— 迁移(把数据/服务从一处搬到另一处)' }]),
|
|
146
|
-
list(0, [{ text: '<b>ran long</b> —— 跑得比预期久(ran = run 的过去式,long 指耗时超出预期)' }]),
|
|
147
|
-
p('合起来:<b>迁移跑得比预期久</b>。'),
|
|
148
|
-
callout('在本句中', '迁移作业耗时超出了原计划——这是在陈述进度延迟的事实,不含"失败了"的意思。'),
|
|
149
|
-
].join('\n'),
|
|
150
|
-
),
|
|
151
|
-
expand,
|
|
152
|
-
askRow(false),
|
|
153
|
-
].join('\n'),
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
// ───────────────────────── 状态二:展开详解 + 追问 ─────────────────────────
|
|
157
|
-
const stage2 = panel(
|
|
158
|
-
'detail',
|
|
159
|
-
[
|
|
160
|
-
quote('the migration ran long'),
|
|
161
|
-
section(
|
|
162
|
-
'translation',
|
|
163
|
-
'翻译',
|
|
164
|
-
[
|
|
165
|
-
p('这句话里需要翻译的是 <b>the migration ran long</b>:'),
|
|
166
|
-
list(0, [{ text: '<b>migration</b> /maɪˈɡreɪʃn/ —— 迁移(把数据/服务从一处搬到另一处)' }]),
|
|
167
|
-
list(0, [{ text: '<b>ran long</b> —— 跑得比预期久(ran = run 的过去式,long 指耗时超出预期)' }]),
|
|
168
|
-
callout('在本句中', '迁移作业耗时超出了原计划——这是在陈述进度延迟的事实,不含"失败了"的意思。'),
|
|
169
|
-
].join('\n'),
|
|
170
|
-
),
|
|
171
|
-
section(
|
|
172
|
-
'detail',
|
|
173
|
-
'详解',
|
|
174
|
-
[
|
|
175
|
-
p('这句话出自一句对话,是开发者在向 PM 说明<b>进度为什么需要重新安排</b>——它不是报错,而是在为后面的决定做铺垫。'),
|
|
176
|
-
p('两个关键说法各指什么:'),
|
|
177
|
-
list(0, [
|
|
178
|
-
{ text: '<b>migration</b>(迁移):把数据或服务从旧结构搬到新结构的一次作业。在数据库语境里通常指改表结构、搬数据这类"不能中途停"的操作。' },
|
|
179
|
-
{ text: '<b>ran long</b>(跑久了):完成时态,说的是这次已经发生的执行<b>耗时超过预期</b>,而不是"即将变慢"。' },
|
|
180
|
-
]),
|
|
181
|
-
p('承接关系:既然迁移比预期久,后面的 <b>freeze features</b>(冻结功能开发)就不是随口一说,而是把人力腾出来的实际动作。'),
|
|
182
|
-
callout('小结', '在说"迁移耗时超预期",用来解释接下来的功能冻结——重点在<b>排期</b>,不在迁移本身的对错。'),
|
|
183
|
-
].join('\n'),
|
|
184
|
-
),
|
|
185
|
-
` <div class="dsh-sel-chatlog" style="display:flex">`,
|
|
186
|
-
chatBubble('user', '那 freeze features 是指完全停止开发吗?'),
|
|
187
|
-
chatBubble('assistant', '不是完全停止,而是<b>暂停新功能合入</b>:已有功能的修 bug 与收尾照常,只是不再往主干加新特性,好让迁移期间的分支收敛。'),
|
|
188
|
-
' </div>',
|
|
189
|
-
askRow(false),
|
|
190
|
-
].join('\n'),
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
/** 代码块:真实渲染器逐行输出 `.dsh-sel-pre-line`,注释行带 data-wrap(可折行),代码行不折。 */
|
|
194
|
-
const codeBlock = (lines) => ` <pre class="dsh-sel-pre" data-hl="light">${lines
|
|
195
|
-
.map((l) => `<div class="dsh-sel-pre-line"${l.wrap ? ' data-wrap="1"' : ''}>${l.wrap ? `<span class="dsh-sel-pre-cmt">${l.text}</span>` : l.text}</div>`)
|
|
196
|
-
.join('')}</pre>`
|
|
197
|
-
|
|
198
|
-
// ───────────────────────── 状态三:选中代码 → 出注释 ─────────────────────────
|
|
199
|
-
const codeDemo = panel(
|
|
200
|
-
'translation',
|
|
201
|
-
[
|
|
202
|
-
quote('function debounce(fn, wait) { … }'),
|
|
203
|
-
section(
|
|
204
|
-
'translation',
|
|
205
|
-
'注释',
|
|
206
|
-
[
|
|
207
|
-
codeBlock([
|
|
208
|
-
{ wrap: true, text: '// 防抖:把连续触发压成「最后一次之后 wait 毫秒才真正执行」' },
|
|
209
|
-
{ text: 'function debounce(fn, wait) {' },
|
|
210
|
-
{ wrap: true, text: ' // 闭包变量:保存上一次的定时器,用来取消它' },
|
|
211
|
-
{ text: ' let timer = null' },
|
|
212
|
-
{ wrap: true, text: ' // 每次触发都先清掉上一个定时器,再重新计时' },
|
|
213
|
-
{ text: ' return function (...args) {' },
|
|
214
|
-
{ text: ' clearTimeout(timer)' },
|
|
215
|
-
{ wrap: true, text: ' // wait 毫秒内没有新触发,才执行原函数' },
|
|
216
|
-
{ text: ' timer = setTimeout(() => fn.apply(this, args), wait)' },
|
|
217
|
-
{ text: ' }' },
|
|
218
|
-
{ text: '}' },
|
|
219
|
-
]),
|
|
220
|
-
callout('小结', '把高频触发收敛成一次执行——常用于搜索输入、窗口 resize 这类"只要最终结果"的场景。'),
|
|
221
|
-
].join('\n'),
|
|
222
|
-
),
|
|
223
|
-
].join('\n'),
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
/** 网页模式:回答里嵌一张可交互的 HTML 页面(.dsh-sel-preview 结构照渲染器)。 */
|
|
227
|
-
const previewBlock = (srcdoc) => ` <div class="dsh-sel-preview" data-view="preview" data-full="1">
|
|
228
|
-
<div class="dsh-sel-previewbar">
|
|
229
|
-
<div class="dsh-sel-previewtabs">
|
|
230
|
-
<button class="dsh-sel-previewtab" data-on="1">预览</button>
|
|
231
|
-
<button class="dsh-sel-previewtab" data-on="0">源码</button>
|
|
232
|
-
</div>
|
|
233
|
-
<button class="dsh-sel-previewzoom">⤡ 还原</button>
|
|
234
|
-
</div>
|
|
235
|
-
<div class="dsh-sel-previewbody">
|
|
236
|
-
<iframe class="dsh-sel-previewframe" srcdoc="${srcdoc.replace(/"/g, '"')}"></iframe>
|
|
237
|
-
</div>
|
|
238
|
-
</div>`
|
|
239
|
-
|
|
240
|
-
const DEMO_HTML = `<!doctype html><html><head><meta charset="utf-8"><style>
|
|
241
|
-
body{margin:0;padding:14px 16px;font:13px/1.6 -apple-system,"PingFang SC",sans-serif;color:#1a1a1a;background:#fff}
|
|
242
|
-
h3{margin:0 0 4px;font-size:14px}
|
|
243
|
-
p.sub{margin:0 0 12px;color:#6b7280;font-size:12px}
|
|
244
|
-
table{border-collapse:collapse;width:100%;font-size:12.5px}
|
|
245
|
-
th,td{border-bottom:1px solid #e6e8eb;padding:7px 8px;text-align:left}
|
|
246
|
-
th{background:#f6f8f9;font-weight:600;color:#374151}
|
|
247
|
-
td.y{color:#0f766e;font-weight:600}
|
|
248
|
-
td.n{color:#b45309}
|
|
249
|
-
.bars{display:flex;align-items:flex-end;gap:10px;height:74px;margin:14px 0 4px}
|
|
250
|
-
.bar{flex:1;border-radius:5px 5px 0 0;background:#0d9488;position:relative}
|
|
251
|
-
.bar span{position:absolute;bottom:-19px;left:0;right:0;text-align:center;font-size:11px;color:#6b7280}
|
|
252
|
-
.b1{height:34px;background:#99f6e4}.b2{height:58px}.b3{height:74px;background:#0f766e}
|
|
253
|
-
</style></head><body>
|
|
254
|
-
<h3>三种缓存策略的取舍</h3>
|
|
255
|
-
<p class="sub">按"命中率 / 一致性风险 / 实现成本"三个维度对比</p>
|
|
256
|
-
<table>
|
|
257
|
-
<tr><th>策略</th><th>命中率</th><th>一致性风险</th><th>实现成本</th></tr>
|
|
258
|
-
<tr><td>不缓存</td><td class="n">—</td><td class="y">无</td><td class="y">最低</td></tr>
|
|
259
|
-
<tr><td>进程内 LRU</td><td class="y">高</td><td class="n">中(多实例不一致)</td><td class="y">低</td></tr>
|
|
260
|
-
<tr><td>共享缓存 + TTL</td><td class="y">最高</td><td class="y">低(可配 TTL)</td><td class="n">中</td></tr>
|
|
261
|
-
</table>
|
|
262
|
-
<div class="bars"><div class="bar b1"><span>不缓存</span></div><div class="bar b2"><span>LRU</span></div><div class="bar b3"><span>共享缓存</span></div></div>
|
|
263
|
-
</body></html>`
|
|
264
|
-
|
|
265
|
-
// ───────────────────────── 状态四:网页模式(输出偏好拨到「网页」)─────────────────────────
|
|
266
|
-
// 场景要能体现"为什么该用网页":需要对比 + 图示的问题 → 直接给一页 HTML。
|
|
267
|
-
const webDemo = panel(
|
|
268
|
-
'detail',
|
|
269
|
-
[
|
|
270
|
-
quote('缓存策略'),
|
|
271
|
-
section(
|
|
272
|
-
'detail',
|
|
273
|
-
'详解',
|
|
274
|
-
[
|
|
275
|
-
p('三种做法的差别是并列的,画成一页更好看:'),
|
|
276
|
-
previewBlock(DEMO_HTML),
|
|
277
|
-
].join('\n'),
|
|
278
|
-
),
|
|
279
|
-
` <div class="dsh-sel-chatlog" style="display:flex">`,
|
|
280
|
-
chatBubble('user', '把三种缓存策略的差别画成一页对比'),
|
|
281
|
-
' </div>',
|
|
282
|
-
askRow(true, 'deepseek-v4.1-flash', '高'),
|
|
283
|
-
].join('\n'),
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
// ───────────────────────── 状态五:引用(❝)─────────────────────────
|
|
287
|
-
// 三个入口同框:气泡里的引用块(用户消息)、输入框上方的引用卡片、助手气泡末尾的「引用整条」。
|
|
288
|
-
// 浮标照真实行为摆:浮在"被划中的那段"右上角(真实浮层里它是 position:fixed,
|
|
289
|
-
// 演示页里改成绝对定位贴在回答上,好让一张图里同时看见三个入口)。
|
|
290
|
-
const quoteChip = (src, text) => `
|
|
291
|
-
<div class="dsh-sel-quotechip" data-quote="1">
|
|
292
|
-
<span class="dsh-sel-quotechip-src">❝ ${src}</span>
|
|
293
|
-
<span class="dsh-sel-quotechip-text" title="${text}">${text}</span>
|
|
294
|
-
<button class="dsh-sel-quotechip-x" type="button" aria-label="移除这条引用">✕</button>
|
|
295
|
-
</div>`
|
|
296
|
-
|
|
297
|
-
const bqItem = (src, text) => `<div class="dsh-sel-bqitem" title="${text}"><b>❝ ${src}</b>${text}</div>`
|
|
298
|
-
|
|
299
|
-
const quoteIcon = `<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" style="width:13px;height:13px;display:block"><circle cx="5.4" cy="6.1" r="2.3"></circle><path d="M3.9 7.4 2.1 12.2 6.1 10.2z"></path><circle cx="11.6" cy="6.1" r="2.3"></circle><path d="M10.1 7.4 8.3 12.2 12.3 10.2z"></path></svg>`
|
|
300
|
-
|
|
301
|
-
const quoteFloat = `<button class="dsh-sel-btn dsh-sel-quotebtn" type="button" style="position:absolute;right:8px;top:-20px;display:inline-flex">${quoteIcon}<span>引用</span></button>`
|
|
302
|
-
|
|
303
|
-
const quotesDemo = panel(
|
|
304
|
-
'detail',
|
|
305
|
-
[
|
|
306
|
-
quote('the migration ran long'),
|
|
307
|
-
section(
|
|
308
|
-
'translation',
|
|
309
|
-
'翻译',
|
|
310
|
-
[
|
|
311
|
-
p('需要翻译的是 <b>the migration ran long</b>:<b>迁移跑得比预期久</b>。'),
|
|
312
|
-
callout('在本句中', '迁移作业耗时超出了原计划——陈述进度延迟的事实,不含"失败了"的意思。'),
|
|
313
|
-
].join('\n'),
|
|
314
|
-
),
|
|
315
|
-
` <div class="dsh-sel-chatlog" style="display:flex">`,
|
|
316
|
-
chatBubble('user', '这句话在排期上意味着什么?'),
|
|
317
|
-
chatBubble(
|
|
318
|
-
'assistant',
|
|
319
|
-
`<div style="position:relative"><p>意味着后面的 <b>freeze features</b> 是配套动作:把人力腾出来盯迁移,而不是随口一说。</p>${quoteFloat}</div>` +
|
|
320
|
-
'<button class="dsh-sel-bubquote" type="button" style="opacity:.66"><span>❝ 引用整条</span></button>',
|
|
321
|
-
),
|
|
322
|
-
chatBubble(
|
|
323
|
-
'user',
|
|
324
|
-
`<div class="dsh-sel-bq">${bqItem('小窗回答', '意味着后面的 freeze features 是配套动作:把人力腾出来盯迁移')}</div>` +
|
|
325
|
-
'<div>那这两件事的先后顺序能换吗?</div>',
|
|
326
|
-
),
|
|
327
|
-
' </div>',
|
|
328
|
-
askRow(
|
|
329
|
-
false,
|
|
330
|
-
'deepseek-v4.1-flash',
|
|
331
|
-
'高',
|
|
332
|
-
[quoteChip('小窗回答', '意味着后面的 freeze features 是配套动作…'), quoteChip('主界面选中', 'the migration ran long')].join(''),
|
|
333
|
-
),
|
|
334
|
-
].join('\n'),
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
const css = extractCss()
|
|
338
|
-
mkdirSync(resolve(ROOT, 'docs', 'demo'), { recursive: true })
|
|
339
|
-
const out = {
|
|
340
|
-
'stage1.html': PAGE('划词解读 · 首轮翻译', css, stage1),
|
|
341
|
-
'stage2.html': PAGE('划词解读 · 详解与追问', css, stage2),
|
|
342
|
-
'code.html': PAGE('划词解读 · 代码注释', css, codeDemo),
|
|
343
|
-
'web.html': PAGE('划词解读 · 网页模式', css, webDemo),
|
|
344
|
-
'quotes.html': PAGE('划词解读 · 引用', css, quotesDemo),
|
|
345
|
-
}
|
|
346
|
-
for (const [name, html] of Object.entries(out)) {
|
|
347
|
-
writeFileSync(resolve(ROOT, 'docs', 'demo', name), html, 'utf8')
|
|
348
|
-
console.log(`✓ docs/demo/${name} ${(html.length / 1024).toFixed(1)} kB`)
|
|
349
|
-
}
|
|
350
|
-
console.log(`\nCSS 来源:lib/client.js(${css.length} 字符,未手抄)`)
|