xiaoyuan-assistant 0.5.56 → 0.5.57

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 CHANGED
@@ -455,3 +455,10 @@ ls -lh /mnt/data/xiaoyuan-assistant-siliconflow-v0.5.29.zip
455
455
  - 去掉小园弹窗右上角除最小化之外的静音/停止播报入口,右上角仅保留收起按钮。
456
456
  - 升级收起状态的小园悬浮图标:增加呼吸光晕、扩散聆听波纹、轨道光点和动态音波,默认有轻微动效。
457
457
  - 当处于监听/处理状态时,收起图标自动加快波纹与音波动画,强化“正在聆听/工作中”的视觉反馈。
458
+
459
+
460
+ ## v0.5.57
461
+
462
+ - 收起状态小园悬浮图标支持按住鼠标/触控拖拽到屏幕任意位置。
463
+ - 松开后自动吸附到距离最近的左侧或右侧屏幕边界,保持当前垂直位置。
464
+ - 拖拽过程禁用点击误触和过渡动画,提升大屏操作稳定性。
@@ -1275,3 +1275,8 @@ AI 分析开始提示音与数据获取/模型分析并行;多个数据源采
1275
1275
  - 展开面板右上角仅保留“收起/最小化”按钮,不提供静音按钮。
1276
1276
  - 小园收起状态采用高级悬浮球视觉:中心徽记 + 呼吸光晕 + 多层扩散聆听波纹 + 环绕光点 + 动态音波。
1277
1277
  - 空闲状态使用轻量呼吸动画;监听/处理状态自动加速动画,提示用户小园正在聆听或处理。
1278
+
1279
+
1280
+ ## v0.5.57|收起态悬浮图标拖拽
1281
+
1282
+ 收起状态下,小园悬浮图标支持按住拖拽。用户可将图标移动到屏幕任意位置;松开鼠标或触控后,根据图标中心点距离自动吸附到最近的左侧或右侧边界,同时保持当前垂直位置。拖拽过程中不会触发打开小园的点击事件。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaoyuan-assistant",
3
- "version": "0.5.56",
3
+ "version": "0.5.57",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -3,10 +3,12 @@
3
3
  <button
4
4
  v-if="!expanded"
5
5
  class="xy-fab"
6
- :class="stateClass"
6
+ :class="[stateClass, { 'xy-dragging': fabDragging }]"
7
7
  type="button"
8
8
  aria-label="打开小园"
9
- @click="activate"
9
+ :style="fabStyle"
10
+ @pointerdown="onFabPointerDown"
11
+ @click="onFabClick"
10
12
  >
11
13
  <span class="xy-fab-glow"></span>
12
14
  <span class="xy-fab-ring ring-a"></span>
@@ -128,13 +130,22 @@ const runtime =
128
130
  // 如果宿主项目因为 HMR / 重复依赖导致当前模块暂时拿不到注入值,
129
131
  // 组件保持可挂载,并在 mounted 后再次尝试解析,避免整个页面崩溃。
130
132
  const safeRuntime = runtime || { manager: null, speech: null, options: {} }
131
- const XIAOYUAN_SDK_VERSION = '0.5.56'
133
+ const XIAOYUAN_SDK_VERSION = '0.5.57'
132
134
  const { manager, speech, options } = safeRuntime
133
135
 
134
136
  const wakeWord = options.wakeWord || '你好小园'
135
137
  const autoCollapseMs = options.autoCollapseMs ?? 0
136
138
 
137
139
  const expanded = ref(false)
140
+ const fabLeft = ref(null)
141
+ const fabTop = ref(null)
142
+ const fabDragging = ref(false)
143
+ let fabPointerId = null
144
+ let fabStartX = 0
145
+ let fabStartY = 0
146
+ let fabStartLeft = 0
147
+ let fabStartTop = 0
148
+ let fabMoved = false
138
149
  const listening = ref(false)
139
150
  const wakeListening = ref(false)
140
151
  const processing = ref(false)
@@ -150,6 +161,89 @@ let speechQueue = Promise.resolve()
150
161
  let speechQueueToken = 0
151
162
  let commandSessionId = 0
152
163
 
164
+ const fabStyle = computed(() => {
165
+ if (fabLeft.value == null || fabTop.value == null) return undefined
166
+ return {
167
+ left: `${fabLeft.value}px`,
168
+ top: `${fabTop.value}px`,
169
+ right: 'auto'
170
+ }
171
+ })
172
+
173
+ function clampFab(left, top) {
174
+ const size = 66
175
+ const margin = 0
176
+ const maxLeft = Math.max(margin, window.innerWidth - size - margin)
177
+ const maxTop = Math.max(margin, window.innerHeight - size - margin)
178
+ return {
179
+ left: Math.min(Math.max(margin, left), maxLeft),
180
+ top: Math.min(Math.max(margin, top), maxTop)
181
+ }
182
+ }
183
+
184
+ function getFabRect() {
185
+ const el = document.querySelector('.xy-fab')
186
+ return el?.getBoundingClientRect?.() || null
187
+ }
188
+
189
+ function onFabPointerDown(event) {
190
+ if (expanded.value) return
191
+ if (event.button != null && event.button !== 0) return
192
+ const rect = getFabRect()
193
+ if (!rect) return
194
+ fabPointerId = event.pointerId
195
+ fabStartX = event.clientX
196
+ fabStartY = event.clientY
197
+ fabStartLeft = rect.left
198
+ fabStartTop = rect.top
199
+ fabMoved = false
200
+ fabDragging.value = true
201
+ try { event.currentTarget.setPointerCapture?.(event.pointerId) } catch (_) {}
202
+ window.addEventListener('pointermove', onFabPointerMove, { passive: false })
203
+ window.addEventListener('pointerup', onFabPointerUp, { passive: true })
204
+ window.addEventListener('pointercancel', onFabPointerUp, { passive: true })
205
+ }
206
+
207
+ function onFabPointerMove(event) {
208
+ if (!fabDragging.value || (fabPointerId != null && event.pointerId !== fabPointerId)) return
209
+ const dx = event.clientX - fabStartX
210
+ const dy = event.clientY - fabStartY
211
+ if (!fabMoved && Math.hypot(dx, dy) < 4) return
212
+ fabMoved = true
213
+ event.preventDefault?.()
214
+ const next = clampFab(fabStartLeft + dx, fabStartTop + dy)
215
+ fabLeft.value = next.left
216
+ fabTop.value = next.top
217
+ }
218
+
219
+ function onFabPointerUp(event) {
220
+ if (!fabDragging.value || (fabPointerId != null && event.pointerId !== fabPointerId)) return
221
+ const rect = getFabRect()
222
+ const width = 66
223
+ if (rect) {
224
+ const viewportMid = window.innerWidth / 2
225
+ const snappedLeft = rect.left + rect.width / 2 <= viewportMid ? 0 : Math.max(0, window.innerWidth - width)
226
+ const next = clampFab(snappedLeft, rect.top)
227
+ fabLeft.value = next.left
228
+ fabTop.value = next.top
229
+ }
230
+ fabDragging.value = false
231
+ fabPointerId = null
232
+ window.removeEventListener('pointermove', onFabPointerMove)
233
+ window.removeEventListener('pointerup', onFabPointerUp)
234
+ window.removeEventListener('pointercancel', onFabPointerUp)
235
+ }
236
+
237
+ function onFabClick(event) {
238
+ if (fabMoved) {
239
+ event.preventDefault()
240
+ event.stopPropagation()
241
+ fabMoved = false
242
+ return
243
+ }
244
+ activate()
245
+ }
246
+
153
247
  const state = computed(() => {
154
248
  if (processing.value) return 'processing'
155
249
  if (listening.value) return 'listening'
@@ -588,6 +682,9 @@ onMounted(() => {
588
682
  })
589
683
 
590
684
  onBeforeUnmount(() => {
685
+ window.removeEventListener('pointermove', onFabPointerMove)
686
+ window.removeEventListener('pointerup', onFabPointerUp)
687
+ window.removeEventListener('pointercancel', onFabPointerUp)
591
688
  wakeController?.stop?.()
592
689
  speech?.stopSpeaking?.()
593
690
  window.clearTimeout(collapseTimer)
@@ -22,7 +22,9 @@
22
22
  .xy-root textarea { font: inherit; }
23
23
 
24
24
  .xy-fab {
25
- position: relative;
25
+ position: fixed;
26
+ top: 22px;
27
+ right: 22px;
26
28
  width: 66px;
27
29
  height: 66px;
28
30
  border: 1px solid rgba(255,255,255,.24);
@@ -48,6 +50,20 @@
48
50
  filter: saturate(1.08);
49
51
  }
50
52
  .xy-fab:active { transform: translateY(-1px) scale(.985); }
53
+ .xy-fab.xy-dragging {
54
+ cursor: grabbing;
55
+ user-select: none;
56
+ transition: none;
57
+ transform: scale(1.02);
58
+ box-shadow:
59
+ 0 20px 54px rgba(41,31,127,.56),
60
+ 0 0 0 1px rgba(255,255,255,.12) inset,
61
+ 0 0 44px rgba(109,93,252,.40);
62
+ }
63
+ .xy-fab {
64
+ touch-action: none;
65
+ user-select: none;
66
+ }
51
67
  .xy-fab-glow {
52
68
  position: absolute;
53
69
  inset: -18px;
@@ -271,6 +287,7 @@
271
287
 
272
288
  @media (max-width: 720px) {
273
289
  .xy-root { top: 12px; right: 12px; }
290
+ .xy-fab { top: 12px; right: 12px; }
274
291
  .xy-panel { width: calc(100vw - 24px); height: calc(100vh - 24px); min-height: 0; border-radius: 18px; }
275
292
  }
276
293