@dungsil/dsh-plugin-ui-model-picker 0.2.5 → 0.3.0
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 +4 -4
- package/dist/client.js +126 -17
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
DSH Web 입력창에서 모델과 추론 강도를 선택합니다. 하나의 선택 버튼을 누르면 추론 강도 팝오버가 먼저 열리며, 팝오버 안의 모델명을 눌러 모델 목록으로 이동합니다. 모델을 고르면 실제 모델은 바로 바뀌지 않고 추론 강도 슬라이더가 이어서 표시되며,
|
|
5
|
+
DSH Web 입력창에서 모델과 추론 강도를 선택합니다. 하나의 선택 버튼을 누르면 추론 강도 팝오버가 먼저 열리며, 팝오버 안의 모델명을 눌러 모델 목록으로 이동합니다. 모델을 고르면 실제 모델은 바로 바뀌지 않고 추론 강도 슬라이더가 이어서 표시되며, 슬라이더를 놓거나 추론 강도 조절 키를 놓으면 모델과 강도가 함께 적용됩니다. 적용 버튼으로도 확정할 수 있습니다.
|
|
6
6
|
|
|
7
7
|
위 이미지는 플러그인 컴포넌트에 예시 모델 목록과 테마 색상을 제공한 검증 화면을 각각 캡처해 합성했습니다. 표시된 모델명과 추론 단계는 예시이며, 실제 목록은 연결한 제공자가 결정합니다.
|
|
8
8
|
|
|
9
9
|
## 사용 방법
|
|
10
10
|
|
|
11
11
|
1. DSH 플러그인 설치 화면에서 `@dungsil/dsh-plugin-ui-model-picker`를 설치하고 활성화합니다.
|
|
12
|
-
2. 입력창 오른쪽 선택 버튼을 눌러 추론 강도 슬라이더를 엽니다. 방향키와 Home/End 키도 사용할 수
|
|
13
|
-
3. 모델을 바꾸려면 팝오버 상단의 모델명 부분을 누르고 제공자별 모델을 선택합니다. 추론 강도를 지원하는 모델을 고르면 슬라이더가 이어서 나타나며,
|
|
12
|
+
2. 입력창 오른쪽 선택 버튼을 눌러 추론 강도 슬라이더를 엽니다. 원하는 강도로 움직인 뒤 놓으면 자동으로 적용됩니다. 방향키와 Home/End 키도 사용할 수 있으며, 키를 놓으면 적용됩니다. 적용 버튼은 누르지 않아도 됩니다.
|
|
13
|
+
3. 모델을 바꾸려면 팝오버 상단의 모델명 부분을 누르고 제공자별 모델을 선택합니다. 추론 강도를 지원하는 모델을 고르면 슬라이더가 이어서 나타나며, 슬라이더 조작을 마치면 모델과 강도가 함께 적용됩니다. 재설정 아이콘은 기본 추론 강도를 복원합니다.
|
|
14
14
|
|
|
15
15
|
## 동작과 호환성
|
|
16
16
|
|
|
17
17
|
- DSH `0.2.1-rc.5` 이상 `0.3.0` 미만의 Web UI를 지원합니다. 기본 `ui-model-selection` 기능이 활성화되어 있어야 합니다.
|
|
18
18
|
- 공용 모델 디렉터리와 선택 API를 그대로 사용하므로 `/model` 명령과 선택 상태를 공유합니다. 별도 모델 설정을 저장하거나 API 키를 다루지 않습니다.
|
|
19
19
|
- 실제 제공자 목록과 모델별 추론 메타데이터만 사용합니다. 별도 모델 목록, Fast 모드, 고정 추론 단계는 추가하지 않습니다. 추론을 지원하지 않는 모델에는 슬라이더를 표시하지 않으며, 그런 모델은 목록에서 고르는 즉시 적용됩니다.
|
|
20
|
-
- 다른 모델로 바꾸면 그 모델이 지원하는 추론 강도에서 슬라이더가 시작되고,
|
|
20
|
+
- 다른 모델로 바꾸면 그 모델이 지원하는 추론 강도에서 슬라이더가 시작되고, 슬라이더를 놓거나 추론 강도 조절 키를 놓으면 모델 선택이 적용됩니다. 드래그 중에는 표시만 바뀌고 조작을 마칠 때 한 번 저장합니다. 슬라이더를 조작하거나 적용 버튼을 누르지 않고 팝오버를 닫으면 모델은 바뀌지 않습니다. 기본값 재설정은 별도 강도 지정을 해제합니다.
|
|
21
21
|
- Escape, 바깥 클릭, 스크롤, 창 크기 변경으로 메뉴를 닫습니다. 메뉴의 방향키와 Tab 탐색을 지원합니다. 한국어·영어·중국어(간체)와 DSH 테마 색상을 따릅니다.
|
|
22
22
|
- 기본 모델 슬롯만 우선순위로 대체합니다. 제거하면 기본 선택 UI가 돌아오며 모델 선택은 유지됩니다. 같은 슬롯을 대체하는 다른 플러그인과는 함께 사용하지 마세요.
|
|
23
23
|
|
package/dist/client.js
CHANGED
|
@@ -124,13 +124,16 @@ const inject = [
|
|
|
124
124
|
"sessions",
|
|
125
125
|
"remote",
|
|
126
126
|
"remote.session",
|
|
127
|
-
"modelDirectories"
|
|
127
|
+
"modelDirectories",
|
|
128
|
+
"shortcuts"
|
|
128
129
|
];
|
|
129
130
|
const copy = {
|
|
130
131
|
ko: {
|
|
131
132
|
model: "모델 선택",
|
|
132
133
|
effort: "추론 강도",
|
|
133
134
|
choose: "추론 수준 선택",
|
|
135
|
+
configure: "추론 수준 설정",
|
|
136
|
+
openPicker: "모델 피커 열기",
|
|
134
137
|
reset: "기본값으로 재설정",
|
|
135
138
|
loading: "모델을 불러오는 중입니다…",
|
|
136
139
|
empty: "사용할 수 있는 모델이 없습니다.",
|
|
@@ -145,6 +148,8 @@ const copy = {
|
|
|
145
148
|
model: "Select model",
|
|
146
149
|
effort: "Reasoning effort",
|
|
147
150
|
choose: "Select reasoning effort",
|
|
151
|
+
configure: "Configure reasoning effort",
|
|
152
|
+
openPicker: "Open model picker",
|
|
148
153
|
reset: "Reset to default",
|
|
149
154
|
loading: "Loading models…",
|
|
150
155
|
empty: "No models available.",
|
|
@@ -159,6 +164,8 @@ const copy = {
|
|
|
159
164
|
model: "选择模型",
|
|
160
165
|
effort: "推理强度",
|
|
161
166
|
choose: "选择推理级别",
|
|
167
|
+
configure: "设置推理级别",
|
|
168
|
+
openPicker: "打开模型选择器",
|
|
162
169
|
reset: "重置为默认值",
|
|
163
170
|
loading: "正在加载模型…",
|
|
164
171
|
empty: "没有可用的模型。",
|
|
@@ -171,10 +178,79 @@ const copy = {
|
|
|
171
178
|
}
|
|
172
179
|
};
|
|
173
180
|
function apply(ctx) {
|
|
181
|
+
const commandId = "dungsil.model-picker.open";
|
|
182
|
+
const pickers = /* @__PURE__ */ new Map();
|
|
183
|
+
const binding = {
|
|
184
|
+
code: "KeyP",
|
|
185
|
+
modifiers: ["alt"]
|
|
186
|
+
};
|
|
187
|
+
const shortcutCatalog = ctx.shortcuts.runtime === "web" ? ctx.shortcuts.fixedCatalog : ctx.shortcuts.catalog;
|
|
188
|
+
ctx.effect(() => {
|
|
189
|
+
const label = () => {
|
|
190
|
+
const locale = ctx.locale.getSnapshot().active;
|
|
191
|
+
return copy[locale.startsWith("ko") ? "ko" : locale.startsWith("zh") ? "zh" : "en"].openPicker;
|
|
192
|
+
};
|
|
193
|
+
const resolve = () => {
|
|
194
|
+
const visible = [...pickers].filter(([element]) => element.isConnected && element.getClientRects().length > 0);
|
|
195
|
+
if (visible.length !== 1) return { status: "pass" };
|
|
196
|
+
const [, picker] = visible[0];
|
|
197
|
+
if (picker.disabled) return {
|
|
198
|
+
status: "blocked",
|
|
199
|
+
reason: "Model picker is locked or busy"
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
status: "handled",
|
|
203
|
+
run: picker.open
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
if (ctx.shortcuts.runtime === "web") {
|
|
207
|
+
const unregister = ctx.shortcuts.registerFixed({
|
|
208
|
+
id: commandId,
|
|
209
|
+
label,
|
|
210
|
+
keys: ["Alt", "P"],
|
|
211
|
+
bindings: [binding],
|
|
212
|
+
group: "input"
|
|
213
|
+
});
|
|
214
|
+
const unobserve = ctx.shortcuts.observeFixedInput((input) => {
|
|
215
|
+
if (input.type !== "keydown") return;
|
|
216
|
+
const { gesture, context } = input;
|
|
217
|
+
if (context.modal || context.region === "terminal" || gesture.code !== "KeyP" || gesture.secondCode || !gesture.alt || gesture.control || gesture.meta || gesture.shift || gesture.repeat || gesture.composing || gesture.defaultPrevented) return;
|
|
218
|
+
const action = resolve();
|
|
219
|
+
if (action.status === "pass") return;
|
|
220
|
+
input.consume();
|
|
221
|
+
if (action.status === "handled") action.run();
|
|
222
|
+
});
|
|
223
|
+
return () => {
|
|
224
|
+
unobserve();
|
|
225
|
+
unregister();
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return ctx.shortcuts.register({
|
|
229
|
+
id: commandId,
|
|
230
|
+
label,
|
|
231
|
+
aliases: [
|
|
232
|
+
"model",
|
|
233
|
+
"reasoning",
|
|
234
|
+
"모델",
|
|
235
|
+
"추론",
|
|
236
|
+
"模型"
|
|
237
|
+
],
|
|
238
|
+
defaults: {
|
|
239
|
+
"desktop:windows": binding,
|
|
240
|
+
"desktop:macos": binding,
|
|
241
|
+
"desktop:linux": binding
|
|
242
|
+
},
|
|
243
|
+
regions: ["page", "editable"],
|
|
244
|
+
modals: [],
|
|
245
|
+
resolve
|
|
246
|
+
});
|
|
247
|
+
}, "ui-model-picker: shortcut");
|
|
174
248
|
function Picker({ locked, directory }) {
|
|
175
249
|
const subscribe = (0, react.useCallback)((listener) => directory.store.subscribe(listener), [directory]);
|
|
176
250
|
const getSnapshot = (0, react.useCallback)(() => directory.store.getSnapshot(), [directory]);
|
|
177
251
|
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
252
|
+
const subscribeShortcuts = (0, react.useCallback)((listener) => shortcutCatalog.subscribe(listener), [shortcutCatalog]);
|
|
253
|
+
const shortcutKeys = (0, react.useSyncExternalStore)(subscribeShortcuts, () => shortcutCatalog.getSnapshot()).find((entry) => entry.id === commandId)?.keys;
|
|
178
254
|
const { active: locale } = useLocaleSnapshot(ctx.locale);
|
|
179
255
|
const text = copy[locale.startsWith("ko") ? "ko" : locale.startsWith("zh") ? "zh" : "en"];
|
|
180
256
|
const [requestedPanel, setPanel] = (0, react.useState)(null);
|
|
@@ -238,7 +314,7 @@ function apply(ctx) {
|
|
|
238
314
|
const observer = new ResizeObserver(position);
|
|
239
315
|
observer.observe(element);
|
|
240
316
|
const scroll = (event) => {
|
|
241
|
-
if (!element.contains(event.target))
|
|
317
|
+
if (!element.contains(event.target)) position();
|
|
242
318
|
};
|
|
243
319
|
window.addEventListener("resize", close);
|
|
244
320
|
window.addEventListener("scroll", scroll, true);
|
|
@@ -325,6 +401,20 @@ function apply(ctx) {
|
|
|
325
401
|
setPanel("effort");
|
|
326
402
|
directory.load().catch(() => {});
|
|
327
403
|
};
|
|
404
|
+
(0, react.useLayoutEffect)(() => {
|
|
405
|
+
const element = anchor.current;
|
|
406
|
+
if (!element) return;
|
|
407
|
+
pickers.set(element, {
|
|
408
|
+
disabled,
|
|
409
|
+
open: () => {
|
|
410
|
+
if (panel) popup.current?.querySelector("input:not(:disabled), button:not(:disabled)")?.focus();
|
|
411
|
+
else if (trigger.current) show(trigger.current);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
return () => {
|
|
415
|
+
pickers.delete(element);
|
|
416
|
+
};
|
|
417
|
+
});
|
|
328
418
|
const selectEffort = (index) => {
|
|
329
419
|
const target = staged ?? (current ? {
|
|
330
420
|
provider: current.provider,
|
|
@@ -368,21 +458,27 @@ function apply(ctx) {
|
|
|
368
458
|
className: "dsh-codex-picker",
|
|
369
459
|
ref: anchor,
|
|
370
460
|
"aria-busy": busy,
|
|
371
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
461
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
462
|
+
label: efforts.length ? text.configure : text.model,
|
|
463
|
+
shortcutKeys,
|
|
464
|
+
side: "top",
|
|
465
|
+
portal: true,
|
|
466
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
|
|
467
|
+
ref: trigger,
|
|
468
|
+
className: "dsh-codex-trigger",
|
|
469
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutlineRegular, { size: 16 }),
|
|
470
|
+
label: model?.name ?? current?.model ?? text.model,
|
|
471
|
+
detail: effortLabel,
|
|
472
|
+
disabled: locked,
|
|
473
|
+
"aria-disabled": disabled,
|
|
474
|
+
"aria-label": `${efforts.length ? text.choose : text.model}: ${model?.name ?? current?.model ?? text.model}${effortLabel ? `, ${effortLabel}` : ""}`,
|
|
475
|
+
"aria-haspopup": "dialog",
|
|
476
|
+
"aria-expanded": panel !== null,
|
|
477
|
+
"aria-controls": panel ? id : void 0,
|
|
478
|
+
onClick: (event) => {
|
|
479
|
+
if (!disabled) show(event.currentTarget);
|
|
480
|
+
}
|
|
481
|
+
})
|
|
386
482
|
})
|
|
387
483
|
}), panel && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
|
|
388
484
|
id,
|
|
@@ -530,6 +626,19 @@ function apply(ctx) {
|
|
|
530
626
|
"aria-valuetext": stagedEfforts[draft]?.name,
|
|
531
627
|
onChange: (event) => {
|
|
532
628
|
if (!disabled) setDraft(Number(event.target.value));
|
|
629
|
+
},
|
|
630
|
+
onPointerUp: () => selectEffort(draft),
|
|
631
|
+
onKeyUp: (event) => {
|
|
632
|
+
if ([
|
|
633
|
+
"ArrowLeft",
|
|
634
|
+
"ArrowRight",
|
|
635
|
+
"ArrowUp",
|
|
636
|
+
"ArrowDown",
|
|
637
|
+
"Home",
|
|
638
|
+
"End",
|
|
639
|
+
"PageUp",
|
|
640
|
+
"PageDown"
|
|
641
|
+
].includes(event.key)) selectEffort(draft);
|
|
533
642
|
}
|
|
534
643
|
})]
|
|
535
644
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungsil/dsh-plugin-ui-model-picker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Model and reasoning effort picker for the DeepSeek Harness composer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,15 +26,16 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@deepseek-ai/cordis": "4.0
|
|
30
|
-
"@deepseek-ai/dsh-api-session-controller": "0.2.
|
|
31
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
32
|
-
"@deepseek-ai/dsh-client-
|
|
33
|
-
"@deepseek-ai/dsh-client-
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-
|
|
35
|
-
"@deepseek-ai/dsh-client-ui-
|
|
36
|
-
"@deepseek-ai/dsh-client-ui-
|
|
37
|
-
"@deepseek-ai/dsh-client-ui-
|
|
29
|
+
"@deepseek-ai/cordis": "4.1.0",
|
|
30
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.2-rc.1",
|
|
31
|
+
"@deepseek-ai/dsh-client-locale": "0.2.2-rc.1",
|
|
32
|
+
"@deepseek-ai/dsh-client-shortcuts": "0.2.2-rc.1",
|
|
33
|
+
"@deepseek-ai/dsh-client-store": "0.2.2-rc.1",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.2-rc.1",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "0.2.2-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.2-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.2-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.2-rc.1",
|
|
38
39
|
"@dungsil/dsh-tsconfig": "0.0.0",
|
|
39
40
|
"@dungsil/dsh-tsdown": "0.0.0",
|
|
40
41
|
"@dungsil/dsh-ui": "0.0.0",
|
|
@@ -46,10 +47,10 @@
|
|
|
46
47
|
"tsdown": "0.23.4"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5"
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5 || ^0.2.2-rc.1"
|
|
50
51
|
},
|
|
51
52
|
"engines": {
|
|
52
|
-
"dsh": "^0.2.
|
|
53
|
+
"dsh": "^0.2.2-rc.1",
|
|
53
54
|
"node": ">=24.9.0"
|
|
54
55
|
},
|
|
55
56
|
"dsh": {
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"immediately": true,
|
|
62
63
|
"inject": [
|
|
63
64
|
"@deepseek-ai/dsh-client-locale",
|
|
65
|
+
"@deepseek-ai/dsh-client-shortcuts",
|
|
64
66
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
65
67
|
"@deepseek-ai/dsh-client-ui-session",
|
|
66
68
|
"@deepseek-ai/dsh-client-ui-model-selection"
|