dsh-music-player 0.3.0 → 0.3.2

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 +5 -3
  2. package/lib/client.js +83 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
4
 
5
- DeepSeek Harness 本地音乐 + AI 讲书插件(bundle)。
5
+ DeepSeek Harness 本地音乐/小说播放插件。
6
6
 
7
- Host 进程扫描本地音乐目录(默认 `~/Music`,可在面板里改)并以 HTTP Range 流式给浏览器提供音频;浏览器侧提供**正在播放条**(上一首/播放暂停/下一首/停止、播放模式、音量、实时频谱)与浮动的**播放面板**(曲目列表 / 自建歌单 / 目录设置)。支持 **AI 讲书**:本地 `.txt` 小说经 MiMo TTS 合成朗读,自动识别书名/前言/章节/尾声,可跳章、可选声音。同时注册 `music_play` 模型工具,让 agent 可按关键词播放音乐或启动讲书。
7
+ 写代码写累了、想摸鱼又不想切窗口?这个插件就是你的**摸鱼神器**——直接在 DeepSeek Harness 的网页里塞进一个本地音乐播放器:扫一下你电脑上的音乐目录(默认 `~/Music`)就能在浏览器里听歌,带播放条和可拖拽的播放面板,还能自己建歌单。
8
+
9
+ 光听歌还不够,它还能**听书**:把本地 `.txt` 小说丢给 AI 朗读,想听哪章点哪章、声音随便挑。最绝的是它还注册了 `music_play` 模型工具——你连鼠标都不用动,在对话框里跟 agent 说句「播放周杰伦的歌」,音乐分分钟响起来,摸鱼摸出新境界。
8
10
 
9
11
  ## 特性
10
12
 
@@ -12,7 +14,7 @@ DeepSeek Harness 本地音乐 + AI 讲书插件(bundle)。
12
14
  - 顺序播放、单曲循环、乱序播放三种模式
13
15
  - 实时 7 段频谱可视化(解码音频包络驱动)
14
16
  - 播放时申请屏幕唤醒锁,防止听歌时熄屏/休眠(支持 Wake Lock 的浏览器,如 Chrome/Edge)
15
- - 播放列表面板可自由拖动,位置跨刷新记忆
17
+ - 播放列表面板可自由拖动,右下角可拖拽调整大小,位置与尺寸跨刷新记忆
16
18
  - AI 讲书:本地 `.txt` 小说经 MiMo TTS 合成朗读,自动识别**书名/前言/章节/尾声**结构,播放条带**章节目录**跳转、章节切歌,可选 4 种中文 AI 声音(默认白桦)
17
19
  - `music_play` 模型工具:agent 可按关键词播放本地音乐,也可按小说名启动 AI 讲书
18
20
  - 支持的格式:`mp3 / m4a / m4b / aac / flac / wav / ogg / opus / webm / aiff`(自动递归扫描子目录,上限 500 首)
package/lib/client.js CHANGED
@@ -101,14 +101,24 @@ window.__ModuleLoader__.load({
101
101
  }
102
102
  return best;
103
103
  }
104
- // Restore the playback-panel position ({x,y,h}) previously saved by dragging, if any.
104
+ // Playback-panel geometry: default CSS width (must match .dsh-music-panel),
105
+ // resize bounds, and the viewport-height fraction cap when user-resized.
106
+ const PANEL_W = 380;
107
+ const PANEL_MIN_W = 280;
108
+ const PANEL_MAX_W = 640;
109
+ const PANEL_MIN_H = 200;
110
+ const PANEL_MAX_H_VH = 0.8;
111
+ // Restore the playback-panel position ({x,y,w,h}) previously saved by
112
+ // dragging/resizing, if any. Old saves ({x,y,h}) migrate with a default width.
105
113
  function loadPanelPos() {
106
114
  const raw = loadPref(PREF_PANEL_POS);
107
115
  if (raw === null) return null;
108
116
  try {
109
117
  const p = JSON.parse(raw);
110
118
  if (p && typeof p.x === 'number' && typeof p.y === 'number'
111
- && typeof p.h === 'number' && p.h > 0) return p;
119
+ && typeof p.h === 'number' && p.h > 0) {
120
+ return { x: p.x, y: p.y, w: (typeof p.w === 'number' && p.w > 0) ? p.w : PANEL_W, h: p.h };
121
+ }
112
122
  } catch (e) {}
113
123
  return null;
114
124
  }
@@ -1631,9 +1641,11 @@ window.__ModuleLoader__.load({
1631
1641
  const isBook = s.currentId !== null && String(s.currentId).startsWith('book:');
1632
1642
  const listRef = useRef(null);
1633
1643
  const panelRef = useRef(null);
1634
- // Draggable panel position ({x, y, h} left/top/height once dragged; null = default right/bottom)
1644
+ // Draggable panel position + size ({x, y, w, h} left/top/width/height once
1645
+ // dragged or resized; null = default right/bottom, 380px, auto height).
1635
1646
  const [pos, setPos] = useState(loadPanelPos);
1636
- const dragRef = useRef(null);
1647
+ const dragRef = useRef(null); // head-drag state
1648
+ const resizeRef = useRef(null); // corner-resize state
1637
1649
  // 曲库每行「+」打开的「加入歌单」菜单:{track, x, y}(锚点=按钮右上角视口坐标)。
1638
1650
  const [addMenu, setAddMenu] = useState(null);
1639
1651
  const openAddMenu = (track, e) => {
@@ -1641,11 +1653,11 @@ window.__ModuleLoader__.load({
1641
1653
  setAddMenu({ track, x: r.right, y: r.top });
1642
1654
  };
1643
1655
 
1644
- // Once the panel is dragged we switch from CSS right/bottom anchoring to an
1645
- // explicit left/top/height. Locking the height matters: with only top+left set
1646
- // and no height, a fixed element whose CSS also sets bottom collapses to fit
1647
- // the leftover space, so the panel's height jumps while dragging.
1648
- const style = pos === null ? null : { left: pos.x, top: pos.y, height: pos.h };
1656
+ // Once the panel is dragged/resized we switch from CSS right/bottom anchoring
1657
+ // to explicit left/top/width/height. Locking height and clearing max-height
1658
+ // matters: with only top+left and the CSS max-height:72vh still applying, a
1659
+ // fixed element whose CSS also sets bottom would collapse/clamp while dragging.
1660
+ const style = pos === null ? null : { left: pos.x, top: pos.y, width: pos.w, height: pos.h, maxHeight: 'none' };
1649
1661
 
1650
1662
  const onHeadDown = (e) => {
1651
1663
  if (e.button !== undefined && e.button !== 0) return;
@@ -1654,15 +1666,16 @@ window.__ModuleLoader__.load({
1654
1666
  const el = panelRef.current;
1655
1667
  if (el === null) return;
1656
1668
  const rect = el.getBoundingClientRect();
1669
+ const w = pos !== null ? pos.w : PANEL_W;
1657
1670
  const h = pos !== null ? pos.h : rect.height;
1658
- const next = { x: pos !== null ? pos.x : rect.left, y: pos !== null ? pos.y : rect.top, h };
1671
+ const next = { x: pos !== null ? pos.x : rect.left, y: pos !== null ? pos.y : rect.top, w, h };
1659
1672
  dragRef.current = {
1660
1673
  startX: e.clientX, startY: e.clientY,
1661
- originX: next.x, originY: next.y, h,
1674
+ originX: next.x, originY: next.y, w, h,
1662
1675
  };
1663
1676
  setPos(next);
1664
1677
  savePref(PREF_PANEL_POS, JSON.stringify(next));
1665
- e.currentTarget.setPointerCapture(e.pointerId);
1678
+ if (typeof e.currentTarget.setPointerCapture === 'function') e.currentTarget.setPointerCapture(e.pointerId);
1666
1679
  };
1667
1680
  const onHeadMove = (e) => {
1668
1681
  const d = dragRef.current;
@@ -1674,13 +1687,56 @@ window.__ModuleLoader__.load({
1674
1687
  x = Math.max(0, Math.min(x, window.innerWidth - el.offsetWidth));
1675
1688
  y = Math.max(0, Math.min(y, window.innerHeight - el.offsetHeight));
1676
1689
  }
1677
- const next = { x, y, h: d.h };
1690
+ const next = { x, y, w: d.w, h: d.h };
1678
1691
  setPos(next);
1679
1692
  savePref(PREF_PANEL_POS, JSON.stringify(next));
1680
1693
  };
1681
1694
  const onHeadUp = (e) => {
1682
1695
  dragRef.current = null;
1683
- if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1696
+ if (typeof e.currentTarget.releasePointerCapture === 'function'
1697
+ && typeof e.currentTarget.hasPointerCapture === 'function'
1698
+ && e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1699
+ };
1700
+
1701
+ // Corner drag-to-resize: grow/shrink width & height from the bottom-right
1702
+ // handle, clamped to [min, max] and kept inside the viewport. The panel's
1703
+ // top-left (x/y) is untouched by a resize.
1704
+ const onResizeDown = (e) => {
1705
+ if (e.button !== undefined && e.button !== 0) return;
1706
+ const el = panelRef.current;
1707
+ if (el === null) return;
1708
+ const rect = el.getBoundingClientRect();
1709
+ const cur = {
1710
+ x: pos !== null ? pos.x : rect.left,
1711
+ y: pos !== null ? pos.y : rect.top,
1712
+ w: pos !== null ? pos.w : PANEL_W,
1713
+ h: pos !== null ? pos.h : rect.height,
1714
+ };
1715
+ resizeRef.current = { startX: e.clientX, startY: e.clientY, originW: cur.w, originH: cur.h };
1716
+ setPos(cur);
1717
+ savePref(PREF_PANEL_POS, JSON.stringify(cur));
1718
+ if (typeof e.currentTarget.setPointerCapture === 'function') e.currentTarget.setPointerCapture(e.pointerId);
1719
+ };
1720
+ const onResizeMove = (e) => {
1721
+ const d = resizeRef.current;
1722
+ if (d === null) return;
1723
+ const el = panelRef.current;
1724
+ const x = pos !== null ? pos.x : (el !== null ? el.getBoundingClientRect().left : 0);
1725
+ const y = pos !== null ? pos.y : (el !== null ? el.getBoundingClientRect().top : 0);
1726
+ const vw = window.innerWidth, vh = window.innerHeight;
1727
+ const maxW = Math.min(PANEL_MAX_W, Math.max(PANEL_MIN_W, vw - x));
1728
+ const maxH = Math.min(Math.floor(vh * PANEL_MAX_H_VH), Math.max(PANEL_MIN_H, vh - y));
1729
+ const w = Math.max(PANEL_MIN_W, Math.min(d.originW + (e.clientX - d.startX), maxW));
1730
+ const h = Math.max(PANEL_MIN_H, Math.min(d.originH + (e.clientY - d.startY), maxH));
1731
+ const next = { x, y, w, h };
1732
+ setPos(next);
1733
+ savePref(PREF_PANEL_POS, JSON.stringify(next));
1734
+ };
1735
+ const onResizeUp = (e) => {
1736
+ resizeRef.current = null;
1737
+ if (typeof e.currentTarget.releasePointerCapture === 'function'
1738
+ && typeof e.currentTarget.hasPointerCapture === 'function'
1739
+ && e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1684
1740
  };
1685
1741
 
1686
1742
  useEffect(() => {
@@ -1782,7 +1838,8 @@ window.__ModuleLoader__.load({
1782
1838
  // panel (novel status shows on the playback bar instead).
1783
1839
  !isBook && s.error ? React.createElement('div', { className: 'dsh-music-error' }, s.error) : null,
1784
1840
  !isBook && s.loading ? React.createElement('div', { className: 'dsh-music-loading' }, '\u626b\u63cf\u4e2d\u2026') : null,
1785
- React.createElement('div', { className: 'dsh-music-list', ref: (el) => { listRef.current = el; } }, listBody),
1841
+ React.createElement('div', { className: 'dsh-music-list', style: pos === null ? null : { maxHeight: 'none' }, ref: (el) => { listRef.current = el; } }, listBody),
1842
+ React.createElement('div', { className: 'dsh-music-resize', title: '拖动调整面板大小', onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp }),
1786
1843
  addMenu ? React.createElement(AddToPlaylistMenu, {
1787
1844
  track: addMenu.track, anchor: { x: addMenu.x, y: addMenu.y },
1788
1845
  onClose: () => setAddMenu(null),
@@ -2151,7 +2208,7 @@ window.__ModuleLoader__.load({
2151
2208
  // :root (html) it cannot see body's tokens and would always fall back to
2152
2209
  // green. Declared on body, the reference resolves and children inherit
2153
2210
  // the theme's actual brand color.
2154
- 'body { --dsh-music-accent: var(--dsw-alias-brand-primary, #2f9e6e); }\n' +
2211
+ 'body { --dsh-music-accent: var(--dsw-alias-brand-primary, #2f9e6e); --dsh-music-accent-fg: var(--dsw-alias-label-primary-foreground, #fff); }\n' +
2155
2212
  '.dsh-music-bar-wrap { box-sizing: border-box; width: 100%; padding: 0 var(--dsh-composer-side-clearance, 16px); }\n' +
2156
2213
  '.dsh-music-bar { box-sizing: border-box; display: flex; align-items: center; gap: 8px; width: 100%; max-width: var(--dsh-composer-card-max-width, 780px); margin: 0 auto; padding: 4px 10px; font-size: 12px; color: var(--dsw-alias-label-secondary, #8a8f98); background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04)); border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); border-radius: 8px; }\n' +
2157
2214
  '.dsh-music-bar-idle { color: var(--dsw-alias-label-primary, #e6e6e6); font-weight: 500; display: inline-flex; align-items: center; }\n' +
@@ -2188,11 +2245,14 @@ window.__ModuleLoader__.load({
2188
2245
  '.dsh-music-bar .dsh-music-mode-trigger svg { flex: none; }\n' +
2189
2246
  '.dsh-music-bar .dsh-music-mode-menu { align-self: center; }\n' +
2190
2247
  '.dsh-music-panel { position: fixed; right: 24px; bottom: 84px; width: 380px; max-height: 72vh; display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--dsw-alias-bg-overlay, #1e1f22); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35)); border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 13px; z-index: 1000; pointer-events: auto; overflow: hidden; }\n' +
2248
+ '.dsh-music-resize { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; touch-action: none; z-index: 5; }\n' +
2249
+ '.dsh-music-resize::after { content: ""; position: absolute; right: 4px; bottom: 4px; width: 5px; height: 5px; border-right: 2px solid var(--dsw-alias-label-secondary, #8a8f98); border-bottom: 2px solid var(--dsw-alias-label-secondary, #8a8f98); opacity: 0.7; }\n' +
2250
+ '.dsh-music-resize:hover::after { opacity: 1; }\n' +
2191
2251
  '.dsh-music-panel-head { display: flex; align-items: center; gap: 6px; }\n' +
2192
2252
  '.dsh-music-tabs { display: flex; gap: 4px; }\n' +
2193
2253
  '.dsh-music-tab { flex: 1; padding: 5px 0; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 12px; }\n' +
2194
2254
  '.dsh-music-tab:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
2195
- '.dsh-music-tab.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2255
+ '.dsh-music-tab.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2196
2256
  '.dsh-music-panel-drag { cursor: move; touch-action: none; user-select: none; }\n' +
2197
2257
  '.dsh-music-panel-grip { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; letter-spacing: -1px; opacity: 0.7; }\n' +
2198
2258
  '.dsh-music-panel-title { font-weight: 600; margin-right: auto; }\n' +
@@ -2202,11 +2262,11 @@ window.__ModuleLoader__.load({
2202
2262
  '.dsh-music-mode-menu { position: relative; flex: none; }\n' +
2203
2263
  '.dsh-music-mode-menu.right { margin-left: auto; }\n' +
2204
2264
  '.dsh-music-mode-trigger { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.25)); background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.05)); color: var(--dsh-music-accent, #2f9e6e); cursor: pointer; }\n' +
2205
- '.dsh-music-mode-trigger:hover, .dsh-music-mode-trigger.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2265
+ '.dsh-music-mode-trigger:hover, .dsh-music-mode-trigger.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2206
2266
  '.dsh-music-mode-pop { position: absolute; left: 50%; transform: translateX(-50%); bottom: calc(100% + 6px); z-index: 60; display: flex; flex-direction: column; gap: 4px; padding: 6px; background: var(--dsw-alias-bg-overlay, #1e1f22); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35)); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); }\n' +
2207
2267
  '.dsh-music-mode-item { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; }\n' +
2208
2268
  '.dsh-music-mode-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); color: var(--dsh-music-accent, #2f9e6e); }\n' +
2209
- '.dsh-music-mode-item.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2269
+ '.dsh-music-mode-item.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2210
2270
  '.dsh-music-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; min-height: 60px; max-height: 40vh; }\n' +
2211
2271
  '.dsh-music-track { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 6px 8px; border: none; background: transparent; border-radius: 6px; color: var(--dsw-alias-label-primary, #e6e6e6); cursor: pointer; font-size: 12px; }\n' +
2212
2272
  '.dsh-music-track:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
@@ -2219,7 +2279,7 @@ window.__ModuleLoader__.load({
2219
2279
  '.dsh-music-settings { display: flex; flex-direction: column; gap: 10px; }\n' +
2220
2280
  '.dsh-music-settings-row { display: flex; gap: 8px; align-items: center; }\n' +
2221
2281
  '.dsh-music-settings-cur { flex: 1; min-width: 0; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04)); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
2222
- '.dsh-music-settings-btn { padding: 6px 12px; border-radius: 8px; border: none; background: var(--dsh-music-accent, #2f9e6e); color: #fff; cursor: pointer; font-size: 13px; white-space: nowrap; }\n' +
2282
+ '.dsh-music-settings-btn { padding: 6px 12px; border-radius: 8px; border: none; background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); cursor: pointer; font-size: 13px; white-space: nowrap; }\n' +
2223
2283
  '.dsh-music-settings-btn.ghost { background: transparent; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); color: var(--dsw-alias-label-secondary, #8a8f98); }\n' +
2224
2284
  '.dsh-music-picker-overlay { position: absolute; inset: 0; z-index: 70; display: flex; overflow: auto; padding: 16px; background: rgba(0,0,0,0.45); }\n' +
2225
2285
  '.dsh-music-picker { box-sizing: border-box; width: 88%; max-height: 100%; margin: auto; display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--dsw-alias-bg-overlay, #1e1f22); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35)); border-radius: 12px; color: var(--dsw-alias-label-primary, #e6e6e6); }\n' +
@@ -2243,13 +2303,13 @@ window.__ModuleLoader__.load({
2243
2303
  '.dsh-music-toc-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
2244
2304
  '.dsh-music-toc-item.active { color: var(--dsh-music-accent, #2f9e6e); }\n' +
2245
2305
  '.dsh-music-toc-type { flex: none; font-size: 10px; padding: 1px 5px; border-radius: 4px; background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.08)); color: var(--dsw-alias-label-secondary, #8a8f98); }\n' +
2246
- '.dsh-music-toc-item.active .dsh-music-toc-type { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2306
+ '.dsh-music-toc-item.active .dsh-music-toc-type { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2247
2307
  '.dsh-music-toc-heading { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
2248
2308
  // 自建歌单:音乐页子标签 / 歌单详情 / 文件多选 / 播放条收藏
2249
2309
  '.dsh-music-subtabs { display: flex; gap: 4px; flex-wrap: wrap; }\n' +
2250
2310
  '.dsh-music-subtab { flex: none; padding: 4px 10px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); background: transparent; border-radius: 16px; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 12px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
2251
2311
  '.dsh-music-subtab:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
2252
- '.dsh-music-subtab.active { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2312
+ '.dsh-music-subtab.active { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2253
2313
  '.dsh-music-subtab.add { width: 30px; padding: 4px 0; text-align: center; color: var(--dsh-music-accent, #2f9e6e); }\n' +
2254
2314
  '.dsh-music-playlist { display: flex; flex-direction: column; flex: 1; }\n' +
2255
2315
  '.dsh-music-playlist-empty { flex: 1; display: flex; align-items: center; justify-content: center; }\n' +
@@ -2267,7 +2327,7 @@ window.__ModuleLoader__.load({
2267
2327
  '.dsh-music-file-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
2268
2328
  '.dsh-music-file-item.checked { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.1)); color: var(--dsh-music-accent, #2f9e6e); }\n' +
2269
2329
  '.dsh-music-file-check { flex: none; width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.4)); display: inline-flex; align-items: center; justify-content: center; font-size: 10px; }\n' +
2270
- '.dsh-music-file-item.checked .dsh-music-file-check { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
2330
+ '.dsh-music-file-item.checked .dsh-music-file-check { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
2271
2331
  '.dsh-music-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
2272
2332
  // 曲库每行:track 按钮 + 行尾「+」(加入歌单)
2273
2333
  '.dsh-music-track-row { display: flex; align-items: center; gap: 4px; }\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-music-player",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "DeepSeek Harness 本地音乐 + AI 讲书插件:Host 扫描音乐目录并以 HTTP 流式提供音频、解析 .txt 小说结构并经 MiMo TTS 合成朗读,浏览器侧提供播放条/播放面板/章节目录跳转/多声音选择/实时频谱,并注册 music_play 模型工具",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",