dsh-shortcut-extensions 1.0.1 → 1.1.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 +1 -0
- package/README.zh.md +1 -0
- package/client.js +123 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) plugin that adds a keyboard-driven quick-switching layer to the DSH web/desktop UI: a model palette, a session palette with prompt preview, session cycling, find-next-answered, composer focus, and configurable permission cycling — all manageable from a sidebar control panel.
|
|
8
8
|
|
|
9
9
|
中文说明见 [README.zh.md](README.zh.md)。
|
|
10
|
+
- **Header chips** — the current workspace name and git branch sit right beside the session title (`conversation.session.header.actions`); the branch is re-read whenever you enter a session.
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
package/README.zh.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 DSH 网页/桌面界面增加一层**键盘驱动的快速切换**:模型面板、带提问预览的会话面板、会话循环、查找下一个已回答会话、聚焦输入框、可配置的权限循环——全部可以在侧栏控制面板里管理。
|
|
8
8
|
|
|
9
9
|
English documentation: [README.md](README.md)。
|
|
10
|
+
- **页头标签** — 在会话标题右侧显示当前工作区名与 git 分支(`conversation.session.header.actions`),每次进入会话都会重新读取分支。
|
|
10
11
|
|
|
11
12
|
## 功能
|
|
12
13
|
|
package/client.js
CHANGED
|
@@ -579,6 +579,13 @@ window.__ModuleLoader__.load({
|
|
|
579
579
|
'.dqs-rowDot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-state-idle-primary);opacity:.45;}',
|
|
580
580
|
'.dqs-rowDot[data-running="true"]{background:var(--dsw-alias-state-success-primary);opacity:1;}',
|
|
581
581
|
'.dqs-rowPin{flex:none;color:var(--dsw-alias-brand-primary);display:flex;}',
|
|
582
|
+
'.dqs-hdrChip{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 8px;',
|
|
583
|
+
'border-radius:5px;font-size:11px;line-height:1;color:var(--dsw-alias-label-secondary);',
|
|
584
|
+
'background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l1);',
|
|
585
|
+
'max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:6px;}',
|
|
586
|
+
'.dqs-hdrChip:last-child{margin-right:0;}',
|
|
587
|
+
'.dqs-hdrChipLabel{overflow:hidden;text-overflow:ellipsis;}',
|
|
588
|
+
'.dqs-hdrChipIcon{display:flex;flex:none;}',
|
|
582
589
|
'.dqs-badge{flex:none;font-size:11px;color:var(--dsw-alias-state-warn-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:0 8px;line-height:16px;}',
|
|
583
590
|
/* Panel (mirrors the host Plugins page: centered 960px column) */
|
|
584
591
|
'.dqs-page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);display:flex;',
|
|
@@ -1688,6 +1695,109 @@ window.__ModuleLoader__.load({
|
|
|
1688
1695
|
return h('div', { className: 'dqs-hintbar', role: 'status' }, state.text);
|
|
1689
1696
|
}
|
|
1690
1697
|
|
|
1698
|
+
/* ------------------------------------------------------------------ */
|
|
1699
|
+
/* Conversation header chips: workspace name and git branch */
|
|
1700
|
+
/* ------------------------------------------------------------------ */
|
|
1701
|
+
|
|
1702
|
+
const FOLDER_GLYPH = '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />';
|
|
1703
|
+
const BRANCH_GLYPH = '<line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" />';
|
|
1704
|
+
|
|
1705
|
+
function basenameOf(path) {
|
|
1706
|
+
return String(path ?? '').split('/').filter(Boolean).pop() ?? '';
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function workspaceOfSession(workspacesSnap, sessionId) {
|
|
1710
|
+
for (const item of workspacesSnap?.items ?? []) {
|
|
1711
|
+
if ((item?.sessionIds ?? []).includes(sessionId)) {
|
|
1712
|
+
return { title: item.title || basenameOf(item.path), path: item.path };
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return undefined;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
function HeaderGlyph({ markup }) {
|
|
1719
|
+
return h('svg', {
|
|
1720
|
+
width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor',
|
|
1721
|
+
strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true,
|
|
1722
|
+
style: { display: 'block' },
|
|
1723
|
+
dangerouslySetInnerHTML: { __html: markup }
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
function HeaderWorkspaceChip({ ctx }) {
|
|
1728
|
+
const sessionId = useSessionId(ctx);
|
|
1729
|
+
const workspacesSnap = useWorkspacesList(ctx);
|
|
1730
|
+
const sessionsSnap = useSessionsList(ctx);
|
|
1731
|
+
let workspace = sessionId === undefined ? undefined : workspaceOfSession(workspacesSnap, sessionId);
|
|
1732
|
+
if (!workspace && sessionId !== undefined) {
|
|
1733
|
+
const cwd = sessionsSnap?.byId?.[sessionId]?.cwd;
|
|
1734
|
+
const name = basenameOf(cwd);
|
|
1735
|
+
if (name !== '') workspace = { title: name, path: cwd };
|
|
1736
|
+
}
|
|
1737
|
+
if (!workspace) return null;
|
|
1738
|
+
return h('span', { className: 'dqs-hdrChip', title: workspace.path ?? workspace.title },
|
|
1739
|
+
h('span', { className: 'dqs-hdrChipIcon' }, h(HeaderGlyph, { markup: FOLDER_GLYPH })),
|
|
1740
|
+
h('span', { className: 'dqs-hdrChipLabel' }, workspace.title));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/** sessionId -> branch name (or null when unavailable). */
|
|
1744
|
+
const branchCache = new Map();
|
|
1745
|
+
|
|
1746
|
+
async function readGitBranch(ctx, sessionId) {
|
|
1747
|
+
try {
|
|
1748
|
+
const response = await ctx.remote.workspaceFiles.read(
|
|
1749
|
+
sessionId, '.git/HEAD', { offset: 1 }, new AbortController().signal
|
|
1750
|
+
);
|
|
1751
|
+
// Remote calls resolve to a { ok, value } result envelope.
|
|
1752
|
+
if (response && response.ok === false) {
|
|
1753
|
+
const detail = response.error ?? {};
|
|
1754
|
+
return { branch: null, error: `${detail.code ?? 'failed'} ${detail.message ?? ''}`.trim() };
|
|
1755
|
+
}
|
|
1756
|
+
const payload = response?.value ?? response;
|
|
1757
|
+
const text = String(payload?.text ?? '');
|
|
1758
|
+
const ref = /ref:\s*refs\/heads\/(.+)/u.exec(text);
|
|
1759
|
+
if (ref) return { branch: ref[1].trim(), error: null };
|
|
1760
|
+
const trimmed = text.trim();
|
|
1761
|
+
if (trimmed === '') return { branch: null, error: 'empty .git/HEAD' };
|
|
1762
|
+
return { branch: trimmed.slice(0, 7), error: null };
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
return { branch: null, error: String(error?.message ?? error) };
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const branchErrors = new Map();
|
|
1769
|
+
|
|
1770
|
+
function HeaderBranchChip({ ctx }) {
|
|
1771
|
+
const sessionId = useSessionId(ctx);
|
|
1772
|
+
const [state, setState] = useState({ branch: null, error: null });
|
|
1773
|
+
useEffect(() => {
|
|
1774
|
+
if (sessionId === undefined) { setState({ branch: null, error: null }); return undefined; }
|
|
1775
|
+
// Show the cached value immediately, then re-read so a branch switch
|
|
1776
|
+
// made between visits is picked up.
|
|
1777
|
+
if (branchCache.has(sessionId)) {
|
|
1778
|
+
setState({ branch: branchCache.get(sessionId), error: branchErrors.get(sessionId) ?? null });
|
|
1779
|
+
} else {
|
|
1780
|
+
setState({ branch: null, error: null });
|
|
1781
|
+
}
|
|
1782
|
+
let cancelled = false;
|
|
1783
|
+
readGitBranch(ctx, sessionId).then((value) => {
|
|
1784
|
+
if (cancelled) return;
|
|
1785
|
+
branchCache.set(sessionId, value.branch);
|
|
1786
|
+
if (value.error) branchErrors.set(sessionId, value.error);
|
|
1787
|
+
else branchErrors.delete(sessionId);
|
|
1788
|
+
setState(value);
|
|
1789
|
+
});
|
|
1790
|
+
return () => { cancelled = true; };
|
|
1791
|
+
}, [ctx, sessionId]);
|
|
1792
|
+
if (state.branch) {
|
|
1793
|
+
return h('span', { className: 'dqs-hdrChip', title: `git: ${state.branch}` },
|
|
1794
|
+
h('span', { className: 'dqs-hdrChipIcon' }, h(HeaderGlyph, { markup: BRANCH_GLYPH })),
|
|
1795
|
+
h('span', { className: 'dqs-hdrChipLabel' }, state.branch));
|
|
1796
|
+
}
|
|
1797
|
+
// Not a git repository, unreadable, or still loading: stay silent.
|
|
1798
|
+
return null;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1691
1801
|
/* ------------------------------------------------------------------ */
|
|
1692
1802
|
/* Sidebar panel: command deck */
|
|
1693
1803
|
/* ------------------------------------------------------------------ */
|
|
@@ -2289,6 +2399,7 @@ window.__ModuleLoader__.load({
|
|
|
2289
2399
|
'shortcuts',
|
|
2290
2400
|
'remote',
|
|
2291
2401
|
'remote.permissionPresets',
|
|
2402
|
+
'remote.workspaceFiles',
|
|
2292
2403
|
'modelDirectories'
|
|
2293
2404
|
],
|
|
2294
2405
|
apply(ctx) {
|
|
@@ -2316,6 +2427,18 @@ window.__ModuleLoader__.load({
|
|
|
2316
2427
|
return () => { if (style.isConnected) style.remove(); };
|
|
2317
2428
|
}, 'shortcut-ext: styles');
|
|
2318
2429
|
|
|
2430
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
2431
|
+
name: 'conversation.session.header.actions',
|
|
2432
|
+
id: 'shortcut-ext.header-workspace',
|
|
2433
|
+
order: 10
|
|
2434
|
+
}, () => h(HeaderWorkspaceChip, { ctx })));
|
|
2435
|
+
|
|
2436
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
2437
|
+
name: 'conversation.session.header.actions',
|
|
2438
|
+
id: 'shortcut-ext.header-branch',
|
|
2439
|
+
order: 11
|
|
2440
|
+
}, () => h(HeaderBranchChip, { ctx })));
|
|
2441
|
+
|
|
2319
2442
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
2320
2443
|
name: 'shell.overlay',
|
|
2321
2444
|
id: 'shortcut-ext-palette',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-shortcut-extensions",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shortcut Extensions: a toggleable registry of quick-switch shortcut commands (model palette with pins, session palette with prompt preview and archived restore, recency-ordered session cycling, find-next-answered jump, focus composer, configurable permission cycling) with a sidebar control panel and a custom command editor.",
|
|
6
6
|
"keywords": [
|