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 +68 -12
- package/client.rev +1 -1
- package/index.js +335 -83
- package/mirror.js +122 -21
- package/ocr.js +88 -17
- package/package.json +7 -3
- package/selftest.mjs +64 -12
- package/stream.js +103 -32
- package/test/coords.test.mjs +129 -0
- package/test/symbols.test.mjs +73 -0
- package/test/timers.test.mjs +60 -0
package/client.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/* 页面代码的版本号:宿主报的和这里不一致,说明页面还在跑旧模块,自己重载一次。 */
|
|
13
|
-
window.__ADBM_BUILD = '
|
|
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
|
-
|
|
689
|
-
|
|
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 === '' ||
|
|
941
|
+
if (serial === '' || isSending.current) return
|
|
942
|
+
isSending.current = true
|
|
914
943
|
setStatus(L('state.starting'))
|
|
915
944
|
try {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const
|
|
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
|
|
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({
|
|
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
|
-
|
|
1
|
+
152
|