dsh-mobile-use 1.0.1 → 1.0.3

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/client.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  /* 页面代码的版本号:宿主报的和这里不一致,说明页面还在跑旧模块,自己重载一次。 */
13
- window.__ADBM_BUILD = '151'
13
+ window.__ADBM_BUILD = '152'
14
14
 
15
15
  window.__ModuleLoader__.load({
16
16
  id: 'dsh-mobile-use',
@@ -338,6 +338,11 @@ window.__ModuleLoader__.load({
338
338
  const stageRef = React.useRef(null) // ui-copy-ignore
339
339
  const canvasRef = React.useRef(null) // ui-copy-ignore
340
340
  const strokes = React.useRef([])
341
+ /* 标注要绑住"当时看到的那一帧":动笔画第一下就把画面冻住,
342
+ 之后输入说明的几秒里页面继续动,送回会话的图、圈、控件仍然是同一时刻的。 */
343
+ const frozen = React.useRef(null) // ui-copy-ignore
344
+ /* 防重复提交用独立标志:状态行显示"已发送"之后人还能继续圈下一处。 */
345
+ const isSending = React.useRef(false) // ui-copy-ignore
341
346
  /* 点下去先在本地画一个圈:设备那一帧要 0.7 s 才回来,圈让手感立刻有回应。 */
342
347
  const taps = React.useRef([]) // ui-copy-ignore
343
348
  const drawing = React.useRef(null) // ui-copy-ignore
@@ -683,10 +688,15 @@ window.__ModuleLoader__.load({
683
688
  */
684
689
  const devicePoint = (point) => {
685
690
  if (screen.w < 1 || screen.h < 1) return { x: 0, y: 0 }
691
+ const canvas = canvasRef.current
686
692
  const rect = pictureRect()
687
- if (rect.width < 1 || rect.height < 1) return { x: 0, y: 0 }
688
- const u = Math.min(1, Math.max(0, (point.x - rect.left) / rect.width))
689
- const v = Math.min(1, Math.max(0, (point.y - rect.top) / rect.height))
693
+ if (canvas === null || rect.width < 1 || rect.height < 1) return { x: 0, y: 0 }
694
+ /* point 来自 pointOf,是画布内的局部坐标;pictureRect 是视口坐标。
695
+ 混用两个坐标系会把侧栏整体算成负偏移(右栏越靠右,误差越大),
696
+ 所以这里补一次画布自身在视口里的偏移,整条换算只做一次。 */
697
+ const box = canvas.getBoundingClientRect()
698
+ const u = Math.min(1, Math.max(0, (point.x + box.left - rect.left) / rect.width))
699
+ const v = Math.min(1, Math.max(0, (point.y + box.top - rect.top) / rect.height))
690
700
  const x = turned() ? Math.round(v * screen.w) : Math.round(u * screen.w)
691
701
  const y = turned() ? Math.round(u * screen.h) : Math.round(v * screen.h)
692
702
  return { x, y }
@@ -728,6 +738,23 @@ window.__ModuleLoader__.load({
728
738
  if (mode === 'view') return
729
739
  const point = pointOf(event)
730
740
  if (mode === 'mark') {
741
+ /* 第一笔落下就把画面冻住:之后几秒输入说明时页面再动,
742
+ 送回会话的仍是"人看到问题的那一帧"。 */
743
+ if (frozen.current === null && serial !== '') {
744
+ const box = canvasRef.current === null ? null : canvasRef.current.getBoundingClientRect()
745
+ const width = box === null ? 0 : box.width
746
+ fetch(`${ROUTE}/frame?serial=${encodeURIComponent(serial)}`, { cache: 'no-store' })
747
+ .then((response) => (response.ok ? response.blob() : Promise.reject(new Error('no frame'))))
748
+ .then((blob) => createImageBitmap(blob))
749
+ .then((bitmap) => {
750
+ if (frozen.current === null && strokes.current.length > 0) {
751
+ frozen.current = { bitmap, width, at: Date.now() }
752
+ } else if (bitmap !== null && typeof bitmap.close === 'function') {
753
+ bitmap.close()
754
+ }
755
+ })
756
+ .catch(() => { setStatus(L('state.failed')) })
757
+ }
731
758
  drawing.current = [point]
732
759
  strokes.current.push(drawing.current)
733
760
  paint()
@@ -896,6 +923,7 @@ window.__ModuleLoader__.load({
896
923
 
897
924
  const clear = () => {
898
925
  strokes.current = []
926
+ frozen.current = null
899
927
  paint()
900
928
  setStatus('')
901
929
  }
@@ -910,20 +938,34 @@ window.__ModuleLoader__.load({
910
938
  }
911
939
 
912
940
  const send = async () => {
913
- if (serial === '' || status === L('state.sent')) return
941
+ if (serial === '' || isSending.current) return
942
+ isSending.current = true
914
943
  setStatus(L('state.starting'))
915
944
  try {
916
- const response = await fetch(`${ROUTE}/frame?serial=${encodeURIComponent(serial)}`, { cache: 'no-store' })
917
- if (!response.ok) throw new Error('no frame')
918
- const bitmap = await createImageBitmap(await response.blob())
945
+ /* 优先用画第一笔时冻住的那一帧:保证图、圈、控件是同一时刻的现场。
946
+ 冻帧失败(当时没网/没帧)才退回取当前帧,并在证据里标明。 */
947
+ const still = frozen.current
948
+ let bitmap = null
949
+ let frozenAt = 0
950
+ let canvasWidth = 0
951
+ if (still !== null) {
952
+ bitmap = still.bitmap
953
+ frozenAt = still.at
954
+ canvasWidth = still.width
955
+ } else {
956
+ const response = await fetch(`${ROUTE}/frame?serial=${encodeURIComponent(serial)}`, { cache: 'no-store' })
957
+ if (!response.ok) throw new Error('no frame')
958
+ bitmap = await createImageBitmap(await response.blob())
959
+ frozenAt = Date.now()
960
+ canvasWidth = canvasRef.current.getBoundingClientRect().width
961
+ }
919
962
  const out = document.createElement('canvas')
920
963
  out.width = bitmap.width
921
964
  out.height = bitmap.height
922
965
  const ink = out.getContext('2d')
923
966
  if (ink === null) throw new Error('no canvas')
924
967
  ink.drawImage(bitmap, 0, 0)
925
- const box = canvasRef.current.getBoundingClientRect()
926
- const scale = box.width < 1 ? 1 : out.width / box.width
968
+ const scale = canvasWidth < 1 ? 1 : out.width / canvasWidth
927
969
  ink.strokeStyle = '#ff3b30'
928
970
  ink.lineWidth = Math.max(3, 3 * scale)
929
971
  ink.lineCap = 'round'
@@ -959,11 +1001,20 @@ window.__ModuleLoader__.load({
959
1001
  const answer = await fetch(`${ROUTE}/annotate`, {
960
1002
  method: 'POST',
961
1003
  headers: { 'content-type': 'application/json' },
962
- body: JSON.stringify({ serial, session: sessionId, note, png, rect }),
1004
+ body: JSON.stringify({
1005
+ serial,
1006
+ session: sessionId,
1007
+ note,
1008
+ png,
1009
+ rect,
1010
+ frozen_at: frozenAt,
1011
+ frozen_ms: frozenAt < 1 ? null : Math.max(0, Date.now() - frozenAt),
1012
+ }),
963
1013
  }).then((res) => res.json())
964
1014
  if (answer?.ok === true) {
965
1015
  setStatus(L('state.sent'))
966
1016
  strokes.current = []
1017
+ frozen.current = null
967
1018
  setNote('')
968
1019
  paint()
969
1020
  } else {
@@ -971,13 +1022,16 @@ window.__ModuleLoader__.load({
971
1022
  }
972
1023
  } catch {
973
1024
  setStatus(L('state.failed'))
1025
+ } finally {
1026
+ isSending.current = false
974
1027
  }
975
1028
  }
976
1029
 
977
1030
  /* 不画任何东西,直接把最近几秒的画面 + 操作时间线推给 agent:
978
1031
  桌宠变大这类「刚才那几下之后不对劲」的问题,用这个最快。 */
979
1032
  const sendRecent = async () => {
980
- if (serial === '') return
1033
+ if (serial === '' || isSending.current) return
1034
+ isSending.current = true
981
1035
  setStatus(L('state.starting'))
982
1036
  try {
983
1037
  const answer = await fetch(`${ROUTE}/annotate`, {
@@ -993,6 +1047,8 @@ window.__ModuleLoader__.load({
993
1047
  }
994
1048
  } catch {
995
1049
  setStatus(L('state.failed'))
1050
+ } finally {
1051
+ isSending.current = false
996
1052
  }
997
1053
  }
998
1054
 
package/client.rev CHANGED
@@ -1 +1 @@
1
- 151
1
+ 152