@aiwuk/autocomplete 1.0.2 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/client.js +51 -13
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -6,7 +6,7 @@ Part of [AIWUK Team for DSH](https://github.com/walke2019/dsh-capability-team) (
6
6
  dsh plugin add @aiwuk/autocomplete
7
7
  ```
8
8
 
9
- Requires DSH >= 0.2.2-rc.1 (below 0.3.0) and Node.js >= 22.
9
+ Requires DSH >= 0.2.2-rc.1 (below 0.3.0) and Node.js >= 24.9.
10
10
 
11
11
  Composer autocomplete for DSH Web (`@aiwuk/autocomplete`, patch id `autocomplete`). The plugin name, description, settings and on-screen text follow the DSH language (Chinese / English).
12
12
 
@@ -86,4 +86,4 @@ Change them from **Settings → General** or the plugin's page under **Plugins**
86
86
 
87
87
  ## Development
88
88
 
89
- `client.js` is generated from `client/*.js`; edit the fragments and run `npm run build:autocomplete` from the repository root. Tests live in `tests/autocomplete/`.
89
+ `client.js` is generated from `client/*.js` and is not tracked in git. It is built by `pretest`, by `prepack` (so `npm pack` and `npm publish` build it automatically) and by the CI "Build generated files" step. Edit the fragments, then run `npm run build:autocomplete` from the repository root. Tests live in `tests/autocomplete/`.
package/client.js CHANGED
@@ -85,7 +85,11 @@ window.__ModuleLoader__.load({
85
85
  let rect = range.getBoundingClientRect();
86
86
  if (!rect.width && !rect.height) rect = range.getClientRects()[0] ?? rect;
87
87
  if ((!rect.width && !rect.height) || (!rect.top && !rect.left)) {
88
- const node = range.startContainer, offset = range.startOffset;
88
+ let node = range.startContainer, offset = range.startOffset;
89
+ if (node?.nodeType === Node.ELEMENT_NODE && offset > 0) {
90
+ node = node.childNodes[offset - 1] || node;
91
+ offset = node.nodeType === Node.TEXT_NODE ? node.textContent?.length ?? 0 : node.childNodes?.length ?? 0;
92
+ }
89
93
  if (node?.nodeType === Node.TEXT_NODE && offset > 0) {
90
94
  const probe = document.createRange();
91
95
  probe.setStart(node, offset - 1); probe.setEnd(node, offset);
@@ -96,17 +100,17 @@ window.__ModuleLoader__.load({
96
100
  return !rect.width && !rect.height ? null : { left: rect.left, top: rect.top, height: rect.height };
97
101
  }
98
102
  /** The caret position, only when it sits at the end of the draft (continuations append, never splice). */
99
- function caretAtEnd(editor, draft) {
103
+ function caretAtEnd(editor, _draft) {
100
104
  const selection = window.getSelection();
101
105
  if (!selection?.rangeCount) return null;
102
106
  const range = selection.getRangeAt(0);
103
107
  if (!range.collapsed || !editor.contains(range.startContainer)) return null;
104
- const prefixRange = document.createRange();
105
- prefixRange.selectNodeContents(editor);
106
- prefixRange.setEnd(range.startContainer, range.startOffset);
107
- const prefix = collapse(prefixRange.toString().trimEnd());
108
- const rect = caretRect(range);
109
- return rect && prefix && prefix === collapse(draft.trimEnd()) ? rect : null;
108
+ const suffix = document.createRange();
109
+ suffix.selectNodeContents(editor);
110
+ suffix.setStart(range.startContainer, range.startOffset);
111
+ if (suffix.toString().trim() !== '') return null;
112
+ if (!editor.textContent?.trim()) return null;
113
+ return caretRect(range);
110
114
  }
111
115
 
112
116
  const zh = {
@@ -198,7 +202,11 @@ window.__ModuleLoader__.load({
198
202
  function candidateOf(ghost, draft) {
199
203
  const raw = ghost.items.length ? ghost.items[Math.min(ghost.index, ghost.items.length - 1)] ?? ''
200
204
  : ghost.stream.trim().split(/\r?\n/)[0]?.replace(/^[-*0-9.)\s]+/, '') ?? '';
201
- return ghost.mode === 'continue' && raw.startsWith(draft) ? raw.slice(draft.length) : raw;
205
+ if (ghost.mode !== 'continue') return raw;
206
+ if (raw.startsWith(draft)) return raw.slice(draft.length);
207
+ const lowerRaw = raw.toLowerCase(), lowerDraft = draft.toLowerCase();
208
+ if (lowerRaw.startsWith(lowerDraft)) return raw.slice(draft.length);
209
+ return raw;
202
210
  }
203
211
 
204
212
  /** Insert into the shell-owned draft; never submits. Falls back to the editor's own insert on older hosts. */
@@ -266,8 +274,38 @@ window.__ModuleLoader__.load({
266
274
  };
267
275
  measure();
268
276
  window.addEventListener('resize', measure);
269
- return () => window.removeEventListener('resize', measure);
277
+ const scrollEl = anchorRef.current?.closest('[data-composer-card]')?.querySelector('[data-input-scroll]');
278
+ scrollEl?.addEventListener('scroll', measure, { passive: true });
279
+ return () => {
280
+ window.removeEventListener('resize', measure);
281
+ scrollEl?.removeEventListener('scroll', measure);
282
+ };
270
283
  }, [ghost.mode, draft, sessionId, current]);
284
+ React.useLayoutEffect(() => {
285
+ if (typeof document === 'undefined') return undefined;
286
+ const ID = 'aiwuk-autocomplete-ghost-style';
287
+ if (!document.getElementById(ID)) {
288
+ const tag = document.createElement('style');
289
+ tag.id = ID;
290
+ tag.textContent = '[data-aiwuk-ghost-next] [data-composer-placeholder]{visibility:hidden !important;}';
291
+ (document.head || document.documentElement).appendChild(tag);
292
+ }
293
+ const card = anchorRef.current?.closest('[data-composer-card]') || anchorRef.current?.parentElement;
294
+ if (!card) return undefined;
295
+ if (visible && ghost.mode === 'next') card.setAttribute('data-aiwuk-ghost-next', '');
296
+ else card.removeAttribute('data-aiwuk-ghost-next');
297
+ return () => { card.removeAttribute('data-aiwuk-ghost-next'); };
298
+ }, [visible, ghost.mode]);
299
+ React.useEffect(() => {
300
+ if (!visible) return undefined;
301
+ const onSelectionChange = () => {
302
+ const found = composerNear(anchorRef.current);
303
+ if (!found) return;
304
+ if (ghost.mode === 'continue' && !caretAtEnd(found.editor, draft)) setBox(null);
305
+ };
306
+ document.addEventListener('selectionchange', onSelectionChange);
307
+ return () => document.removeEventListener('selectionchange', onSelectionChange);
308
+ }, [visible, ghost.mode, draft]);
271
309
  React.useEffect(() => {
272
310
  if (!visible) return undefined;
273
311
  const onKeyDown = event => {
@@ -298,8 +336,8 @@ window.__ModuleLoader__.load({
298
336
  return h(React.Fragment, null, anchor,
299
337
  h('div', { key: 'ghost', 'data-composer-prediction': ghost.mode, style: { position: 'absolute', top: `${box.top}px`, left: `${box.left}px`,
300
338
  maxWidth: box.maxWidth ? `${box.maxWidth}px` : undefined, height: `${box.height}px`, display: 'flex', alignItems: 'center', gap: '6px',
301
- color: 'var(--dsw-alias-label-tertiary, #888)', pointerEvents: 'none', userSelect: 'none', whiteSpace: 'nowrap', overflow: 'hidden', zIndex: 10 } },
302
- h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', opacity: 0.85 } }, current),
339
+ color: 'var(--dsw-alias-label-tertiary, #888)', pointerEvents: 'none', userSelect: 'none', whiteSpace: 'pre', overflow: 'hidden', zIndex: 10 } },
340
+ h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', opacity: 0.85, whiteSpace: 'pre' } }, current),
303
341
  ghost.items.length > 1 ? h('span', { style: tag }, `${ghost.index + 1}/${ghost.items.length}`) : null,
304
342
  ghost.active ? null : h('kbd', { style: tag }, 'Tab')));
305
343
  }
@@ -1125,7 +1163,7 @@ window.__ModuleLoader__.load({
1125
1163
  },
1126
1164
  };
1127
1165
  // Test-only seam: harnesses may define window.__acTestHook; browsers never do.
1128
- if (typeof window.__acTestHook === 'function') window.__acTestHook({ ComposerOverlay, GhostOverlay, TouchSuggestionBar, TouchDock, TouchAskButton, touchStore, measureCardInset, AutocompleteSettings, parseSse, isTriggerToken, configOf, candidateOf, ENDPOINT, zh, en });
1166
+ if (typeof window.__acTestHook === 'function') window.__acTestHook({ ComposerOverlay, GhostOverlay, TouchSuggestionBar, TouchDock, TouchAskButton, touchStore, measureCardInset, AutocompleteSettings, parseSse, isTriggerToken, configOf, candidateOf, caretAtEnd, caretRect, ENDPOINT, zh, en });
1129
1167
 
1130
1168
  return plugin;
1131
1169
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwuk/autocomplete",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "AIWUK Autocomplete, composer autocomplete for DeepSeek Harness (DSH) with inline continuation and next-message suggestions accepted with Tab, never sent automatically",
6
6
  "license": "MIT",
@@ -9,7 +9,7 @@
9
9
  "repository": { "type": "git", "url": "git+https://github.com/walke2019/dsh-capability-team.git", "directory": "packages/autocomplete" },
10
10
  "bugs": { "url": "https://github.com/walke2019/dsh-capability-team/issues" },
11
11
  "keywords": ["aiwuk", "dsh", "dsh-plugin", "deepseek-harness", "cordis", "autocomplete", "composer", "prediction", "llm"],
12
- "engines": { "node": ">=22", "dsh": ">=0.2.2-rc.1 <0.3.0" },
12
+ "engines": { "node": ">=24.9", "dsh": ">=0.2.2-rc.1 <0.3.0" },
13
13
  "dependencies": { "@deepseek-ai/schemastery": "~3.18.5" },
14
14
  "peerDependencies": {
15
15
  "@deepseek-ai/cordis": ">=4.0.1",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "exports": { ".": "./index.js", "./client": "./client.js", "./locale/*.json": "./locale/*.json", "./i18n/*.json": "./i18n/*.json", "./icon": "./icon.svg" },
25
25
  "files": ["index.js", "predictor.js", "schema.js", "fleet-defaults.js", "managed-route.js", "client.js", "cordis.patch.yml", "locale/*.json", "i18n/*.json", "icon.svg", "README.md", "LICENSE"],
26
- "scripts": { "build": "node scripts/build-client.mjs", "test": "node --test ../../tests/autocomplete/*.test.mjs" },
26
+ "scripts": { "build": "node scripts/build-client.mjs", "prepack": "node ../../shared-ui/build.mjs && node scripts/build-client.mjs", "test": "node --test ../../tests/autocomplete/*.test.mjs" },
27
27
  "dsh": {
28
28
  "bundle": { "patch": "./cordis.patch.yml" },
29
29
  "client": {