dsh-composer-predictions 1.2.2 → 1.2.4
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/CHANGELOG.md +18 -0
- package/README.md +4 -4
- package/client.js +30 -11
- package/package.json +2 -8
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.2.4] - 2026-10-25
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The ghost is anchored to the composer card that owns the overlay, found
|
|
14
|
+
through the overlay's own DOM ancestry. It previously measured the first
|
|
15
|
+
`[data-composer-card]` in the document, so with more than one composer
|
|
16
|
+
mounted (another panel, a transition, a second window) the suggestion could
|
|
17
|
+
paint over another conversation and Tab would consult the wrong card.
|
|
18
|
+
- Switching Sessions also clears the measured box, not just the suggestion.
|
|
19
|
+
|
|
20
|
+
## [1.2.3] - 2026-10-25
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Narrowed package keywords to the six core terms. Runtime behaviour is
|
|
25
|
+
unchanged; this release tests whether npm search indexes the package.
|
|
26
|
+
|
|
9
27
|
## [1.2.2] - 2026-10-25
|
|
10
28
|
|
|
11
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
AI autocomplete and ghost-text suggestions for the composer in **DeepSeek
|
|
8
8
|
Harness desktop and the Web GUI**.
|
|
9
9
|
|
|
10
|
-
**Current release:
|
|
10
|
+
**Current release: [latest on npm](https://www.npmjs.com/package/dsh-composer-predictions)** · [Install or update](#install) · [中文说明](#中文说明)
|
|
11
11
|
|
|
12
12
|
- **Next message** — after the agent replies, the empty composer shows a
|
|
13
13
|
streamed suggestion for your likely next message. **Tab** accepts it as an
|
|
@@ -100,12 +100,12 @@ https://raw.githubusercontent.com/Stijnus/dsh-composer-predictions/main/market-i
|
|
|
100
100
|
`Tab` 采纳已完成的候选为可编辑草稿,不会自动发送;`Alt+↓/↑` 切换最多三个候选,
|
|
101
101
|
`Esc` 关闭。设置 → 通用 → 输入预测可开关,设置保存在当前设备的浏览器中。
|
|
102
102
|
|
|
103
|
-
安装或更新:**插件 → 安装 → `dsh-composer-predictions
|
|
104
|
-
CLI 用户可运行 `dsh plugin --profile web add dsh-composer-predictions
|
|
103
|
+
安装或更新:**插件 → 安装 → `dsh-composer-predictions`**,然后重启 Harness。
|
|
104
|
+
CLI 用户可运行 `dsh plugin --profile web add dsh-composer-predictions`,
|
|
105
105
|
把 `web` 换成自己的 profile。预测会调用会话模型并消耗 token,虽然不会出现在会话日志和 token 统计中。
|
|
106
106
|
|
|
107
107
|
也可以把上方 [Install](#install) 中的市场索引添加到「插件 → 发现」。
|
|
108
|
-
演示、完整配置与限制见本页;[
|
|
108
|
+
演示、完整配置与限制见本页;[最新发布说明](https://github.com/Stijnus/dsh-composer-predictions/releases/latest)。
|
|
109
109
|
|
|
110
110
|
## How it works
|
|
111
111
|
|
package/client.js
CHANGED
|
@@ -162,10 +162,18 @@ window.__ModuleLoader__.load({
|
|
|
162
162
|
opacity: 0.8,
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Locate the composer card this overlay actually belongs to.
|
|
167
|
+
*
|
|
168
|
+
* The seat is rendered inside the resident composer card, so the anchor node
|
|
169
|
+
* is an ancestor-scoped key: a global `querySelector` would pick whichever
|
|
170
|
+
* card happens to be first in the document and paint that conversation's
|
|
171
|
+
* ghost over another one.
|
|
172
|
+
*/
|
|
173
|
+
function composerElementsNear(anchor) {
|
|
167
174
|
if (typeof document === 'undefined') return null
|
|
168
|
-
|
|
175
|
+
if (anchor === null || anchor === undefined || !anchor.isConnected) return null
|
|
176
|
+
const card = anchor.closest('[data-composer-card]')
|
|
169
177
|
if (card === null) return null
|
|
170
178
|
const editor = card.querySelector('[contenteditable="true"]') || card.querySelector('[role="textbox"]')
|
|
171
179
|
return editor === null ? null : { card, editor }
|
|
@@ -364,6 +372,8 @@ window.__ModuleLoader__.load({
|
|
|
364
372
|
const [box, setBox] = React.useState(null)
|
|
365
373
|
const [compositionEnd, setCompositionEnd] = React.useState(0)
|
|
366
374
|
|
|
375
|
+
const anchorRef = React.useRef(null)
|
|
376
|
+
const elements = () => composerElementsNear(anchorRef.current)
|
|
367
377
|
const abortRef = React.useRef(null)
|
|
368
378
|
const composingRef = React.useRef(false)
|
|
369
379
|
const dismissedRef = React.useRef(null)
|
|
@@ -484,6 +494,7 @@ window.__ModuleLoader__.load({
|
|
|
484
494
|
dismissedRef.current = null
|
|
485
495
|
abortRef.current?.abort()
|
|
486
496
|
setGhost(NO_GHOST)
|
|
497
|
+
setBox(null)
|
|
487
498
|
return () => abortRef.current?.abort()
|
|
488
499
|
}, [sessionId])
|
|
489
500
|
|
|
@@ -524,7 +535,7 @@ window.__ModuleLoader__.load({
|
|
|
524
535
|
if (dismissedRef.current !== null && dismissedRef.current.draft === draft) return undefined
|
|
525
536
|
const timer = setTimeout(() => {
|
|
526
537
|
if (composingRef.current) return
|
|
527
|
-
const found =
|
|
538
|
+
const found = elements()
|
|
528
539
|
if (found === null) return
|
|
529
540
|
if (caretAtEnd(found.editor, draft) === null) return
|
|
530
541
|
void request('continue', draft.slice(-MAX_DRAFT_CHARS))
|
|
@@ -564,7 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
564
575
|
React.useLayoutEffect(() => {
|
|
565
576
|
if (ghost.mode !== 'next') return undefined
|
|
566
577
|
const measure = () => {
|
|
567
|
-
const found =
|
|
578
|
+
const found = elements()
|
|
568
579
|
if (found === null) {
|
|
569
580
|
setBox((previous) => (previous === null ? previous : null))
|
|
570
581
|
return
|
|
@@ -590,7 +601,7 @@ window.__ModuleLoader__.load({
|
|
|
590
601
|
setBox((previous) => (sameBox(previous, next) ? previous : next))
|
|
591
602
|
}
|
|
592
603
|
measure()
|
|
593
|
-
const found =
|
|
604
|
+
const found = elements()
|
|
594
605
|
let observer
|
|
595
606
|
if (found !== null && typeof ResizeObserver === 'function') {
|
|
596
607
|
observer = new ResizeObserver(measure)
|
|
@@ -610,7 +621,7 @@ window.__ModuleLoader__.load({
|
|
|
610
621
|
let frame = 0
|
|
611
622
|
const measure = () => {
|
|
612
623
|
frame = 0
|
|
613
|
-
const found =
|
|
624
|
+
const found = elements()
|
|
614
625
|
if (found === null) {
|
|
615
626
|
setBox((previous) => (previous === null ? previous : null))
|
|
616
627
|
return
|
|
@@ -661,7 +672,7 @@ window.__ModuleLoader__.load({
|
|
|
661
672
|
measure()
|
|
662
673
|
document.addEventListener('selectionchange', schedule)
|
|
663
674
|
document.addEventListener('scroll', schedule, true)
|
|
664
|
-
const found =
|
|
675
|
+
const found = elements()
|
|
665
676
|
window.addEventListener('resize', schedule)
|
|
666
677
|
let observer
|
|
667
678
|
if (found !== null && typeof ResizeObserver === 'function') {
|
|
@@ -694,7 +705,7 @@ window.__ModuleLoader__.load({
|
|
|
694
705
|
}
|
|
695
706
|
// Accepting leaves an editable draft: hand the keyboard back to the editor.
|
|
696
707
|
try {
|
|
697
|
-
const found =
|
|
708
|
+
const found = elements()
|
|
698
709
|
if (found !== null && !found.card.contains(document.activeElement)) found.editor.focus()
|
|
699
710
|
} catch {
|
|
700
711
|
// Focus is a courtesy, never a failure.
|
|
@@ -717,7 +728,7 @@ window.__ModuleLoader__.load({
|
|
|
717
728
|
if (event.defaultPrevented) return
|
|
718
729
|
// An IME candidate window owns the keyboard while composing.
|
|
719
730
|
if (event.isComposing === true || event.keyCode === 229) return
|
|
720
|
-
const found =
|
|
731
|
+
const found = elements()
|
|
721
732
|
if (found === null) return
|
|
722
733
|
const target = typeof Node !== 'undefined' && event.target instanceof Node ? event.target : null
|
|
723
734
|
const inEditor = found.editor.contains(document.activeElement)
|
|
@@ -755,7 +766,14 @@ window.__ModuleLoader__.load({
|
|
|
755
766
|
return () => document.removeEventListener('keydown', onKeyDown, true)
|
|
756
767
|
}, [visible, draft, ghost.items.length, ghost.active, ghost.mode])
|
|
757
768
|
|
|
758
|
-
|
|
769
|
+
const anchor = h('span', {
|
|
770
|
+
ref: anchorRef,
|
|
771
|
+
'data-composer-prediction-anchor': '',
|
|
772
|
+
'aria-hidden': 'true',
|
|
773
|
+
style: { display: 'none' },
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
if (!visible || box === null) return anchor
|
|
759
777
|
|
|
760
778
|
const continuation = ghost.mode === 'continue'
|
|
761
779
|
const style = continuation ? CONTINUE_GHOST_STYLE : NEXT_GHOST_STYLE
|
|
@@ -775,6 +793,7 @@ window.__ModuleLoader__.load({
|
|
|
775
793
|
showBadge ? h('span', { style: BADGE_STYLE, title: t('more') }, `${ghost.index + 1}/${ghost.items.length}`) : null,
|
|
776
794
|
showKeycap ? h('kbd', { style: KEYCAP_STYLE, title: t('accept') }, 'Tab') : null,
|
|
777
795
|
),
|
|
796
|
+
anchor,
|
|
778
797
|
)
|
|
779
798
|
}
|
|
780
799
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-composer-predictions",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Tab autocomplete for the DeepSeek Harness desktop and web composer: next-message ghost text, inline draft completion and keyboard candidate cycling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,16 +10,10 @@
|
|
|
10
10
|
"keywords": [
|
|
11
11
|
"dsh",
|
|
12
12
|
"dsh-plugin",
|
|
13
|
-
"dsh-bundle",
|
|
14
13
|
"deepseek-harness",
|
|
15
14
|
"composer",
|
|
16
15
|
"autocomplete",
|
|
17
|
-
"tab-completion"
|
|
18
|
-
"ghost-text",
|
|
19
|
-
"inline-completion",
|
|
20
|
-
"prediction",
|
|
21
|
-
"desktop",
|
|
22
|
-
"input-completion"
|
|
16
|
+
"tab-completion"
|
|
23
17
|
],
|
|
24
18
|
"publishConfig": {
|
|
25
19
|
"access": "public"
|