ruiyun-human 1.0.13 → 1.0.14

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.
@@ -393,15 +393,89 @@ const TTS_MAX_RETRIES = 5;
393
393
  const _sfc_main = {
394
394
  __name: "index",
395
395
  props: {
396
- humans: {
397
- type: Array,
398
- default: () => []
396
+ human: {
397
+ type: Object,
398
+ default: null
399
399
  }
400
400
  },
401
401
  emits: ["loaded", "changed", "paused", "resumed", "audio-ready"],
402
402
  setup(__props, { expose: __expose, emit: __emit }) {
403
403
  const TTS_URL = "http://localhost:6039";
404
404
  const ASR_URL = "http://localhost:6039";
405
+ const HUMAN_PRESETS = {
406
+ 1: {
407
+ humanId: 1,
408
+ title: "西装动漫男",
409
+ // TODO: 替换为真实资源
410
+ folder: "http://192.168.3.107:8088/img/xiangyu-green-suit",
411
+ folder_mobile: "http://192.168.3.107:8088/img/xiangyu-green-suit-header",
412
+ standby: { interval: 100, standbyNum: 11 },
413
+ speak: { interval: 30, speakNum: 0 },
414
+ humanType: 1,
415
+ timbre: 1
416
+ },
417
+ 2: {
418
+ humanId: 2,
419
+ title: "西装动漫女",
420
+ folder: "http://192.168.3.107:8088/img/anime-woman",
421
+ folder_mobile: "http://192.168.3.107:8088/img/anime-woman-header",
422
+ standby: { interval: 100, standbyNum: 1 },
423
+ speak: { interval: 30, speakNum: 75 },
424
+ humanType: 1,
425
+ timbre: 3
426
+ },
427
+ 3: {
428
+ humanId: 3,
429
+ title: "真人形象男",
430
+ folder: "http://192.168.3.107:8088/img/real-man",
431
+ folder_mobile: "http://192.168.3.107:8088/img/real-man-header",
432
+ standby: { interval: 100, standbyNum: 1 },
433
+ speak: { interval: 30, speakNum: 139 },
434
+ humanType: 1,
435
+ timbre: 47
436
+ },
437
+ 4: {
438
+ humanId: 4,
439
+ title: "真人形象女",
440
+ folder: "http://192.168.3.107:8088/img/real-woman",
441
+ folder_mobile: "http://192.168.3.107:8088/img/real-woman-header",
442
+ standby: { interval: 100, standbyNum: 2 },
443
+ speak: { interval: 40, speakNum: 139 },
444
+ humanType: 1,
445
+ timbre: 34
446
+ },
447
+ 5: {
448
+ humanId: 5,
449
+ title: "Q版形象",
450
+ folder: "http://192.168.3.107:8088/img/xiangyu-q",
451
+ folder_mobile: "http://192.168.3.107:8088/img/xiangyu-q-header",
452
+ standby: { interval: 100, standbyNum: 28 },
453
+ speak: { interval: 30, speakNum: 118 },
454
+ humanType: 1,
455
+ timbre: 34
456
+ }
457
+ };
458
+ const isMobile = () => {
459
+ if (typeof navigator === "undefined") return false;
460
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|HarmonyOS/i.test(navigator.userAgent);
461
+ };
462
+ const resolveFolder = (human) => {
463
+ const finalType = human.humanType || (isMobile() ? 2 : 1);
464
+ if (finalType === 2 && human.folder_mobile) return human.folder_mobile;
465
+ return human.folder;
466
+ };
467
+ const resolveHuman = (input) => {
468
+ if (input == null) return null;
469
+ const humanId = typeof input === "number" ? input : input.humanId;
470
+ const preset = HUMAN_PRESETS[humanId];
471
+ if (!preset) {
472
+ console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(HUMAN_PRESETS));
473
+ return null;
474
+ }
475
+ const merged = { ...preset, ...typeof input === "object" ? input : {} };
476
+ merged.folder = resolveFolder(merged);
477
+ return merged;
478
+ };
405
479
  const props = __props;
406
480
  const emit = __emit;
407
481
  const containerRef = vue.ref(null);
@@ -423,27 +497,38 @@ const _sfc_main = {
423
497
  standbyNum: currentHuman.value.standby.standbyNum,
424
498
  speakNum: currentHuman.value.speak.speakNum
425
499
  };
426
- for (let i = 1; i <= standbyNum; i++) {
500
+ const makeImage = (url) => {
427
501
  const image = new Image();
428
- image.src = encodeURI(`${folder}/standby/standby (${i}).png`);
429
- frames.value.standby.push(image);
502
+ image.src = encodeURI(url);
503
+ image.onload = () => {
504
+ image.__broken = false;
505
+ };
506
+ image.onerror = () => {
507
+ image.__broken = true;
508
+ console.warn("[DigitalHuman] 帧加载失败:", url);
509
+ };
510
+ return image;
511
+ };
512
+ for (let i = 1; i <= standbyNum; i++) {
513
+ frames.value.standby.push(makeImage(`${folder}/standby/standby (${i}).png`));
514
+ }
515
+ if (speakNum <= 0) {
516
+ console.warn("[DigitalHuman] 当前形象 speakNum=0,说话动画暂用待机帧替代");
517
+ frames.value.speakStart = [...frames.value.standby];
518
+ frames.value.speakLoop = [...frames.value.standby];
519
+ frames.value.speakEnd = [...frames.value.standby];
520
+ return;
430
521
  }
431
522
  const startEnd = Math.floor(speakNum * 0.6);
432
523
  const loopEnd = Math.floor(speakNum * 0.9);
433
524
  for (let i = 1; i <= startEnd; i++) {
434
- const image = new Image();
435
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
436
- frames.value.speakStart.push(image);
525
+ frames.value.speakStart.push(makeImage(`${folder}/speak/speak (${i}).png`));
437
526
  }
438
527
  for (let i = startEnd; i <= loopEnd; i++) {
439
- const image = new Image();
440
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
441
- frames.value.speakLoop.push(image);
528
+ frames.value.speakLoop.push(makeImage(`${folder}/speak/speak (${i}).png`));
442
529
  }
443
530
  for (let i = loopEnd; i <= speakNum; i++) {
444
- const image = new Image();
445
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
446
- frames.value.speakEnd.push(image);
531
+ frames.value.speakEnd.push(makeImage(`${folder}/speak/speak (${i}).png`));
447
532
  }
448
533
  };
449
534
  const draw = (image) => {
@@ -604,20 +689,38 @@ const _sfc_main = {
604
689
  };
605
690
  allFrames.forEach((img) => {
606
691
  if (img.complete) {
692
+ if (!img.naturalWidth) img.__broken = true;
607
693
  checkLoaded();
608
694
  } else {
609
- img.onload = checkLoaded;
610
- img.onerror = checkLoaded;
695
+ img.onload = () => {
696
+ img.__broken = false;
697
+ checkLoaded();
698
+ };
699
+ img.onerror = () => {
700
+ img.__broken = true;
701
+ checkLoaded();
702
+ };
611
703
  }
612
704
  });
613
705
  };
614
706
  const init = () => {
615
707
  vue.nextTick(() => {
708
+ var _a, _b;
616
709
  if (!containerRef.value || !canvasRef.value) return;
617
- if (!props.humans || props.humans.length === 0) return;
618
- if (!currentHuman.value) {
619
- currentHuman.value = props.humans[0];
710
+ const rawInput = props.human;
711
+ if (!rawInput) return;
712
+ const human = resolveHuman(rawInput);
713
+ if (!human) return;
714
+ const prevType = (_a = currentHuman.value) == null ? void 0 : _a.humanType;
715
+ if (((_b = currentHuman.value) == null ? void 0 : _b.humanId) === human.humanId && prevType === human.humanType) {
716
+ Object.assign(currentHuman.value, human);
717
+ return;
620
718
  }
719
+ abortTTS();
720
+ stop();
721
+ timestamp.value = null;
722
+ frameIndex.value = 1;
723
+ currentHuman.value = human;
621
724
  const canvas = canvasRef.value;
622
725
  const dpr = window.devicePixelRatio || 1;
623
726
  const width = containerRef.value.clientWidth;
@@ -694,6 +797,7 @@ const _sfc_main = {
694
797
  return pcmToWavBlob(buf);
695
798
  };
696
799
  const requestTTSAudio = async (text, signal) => {
800
+ var _a;
697
801
  let lastError = null;
698
802
  for (let attempt = 1; attempt <= TTS_MAX_RETRIES; attempt++) {
699
803
  if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
@@ -704,7 +808,7 @@ const _sfc_main = {
704
808
  clientid: VOICE_CLIENT_ID,
705
809
  "tenant-id": VOICE_TENANT_ID
706
810
  },
707
- body: JSON.stringify({ text, user: TTS_USER, timbre: 47 }),
811
+ body: JSON.stringify({ text, user: TTS_USER, timbre: ((_a = currentHuman.value) == null ? void 0 : _a.timbre) ?? 1 }),
708
812
  signal
709
813
  });
710
814
  if (!res.ok) {
@@ -865,6 +969,27 @@ const _sfc_main = {
865
969
  });
866
970
  }
867
971
  };
972
+ vue.watch(
973
+ () => [props.human],
974
+ () => {
975
+ init();
976
+ },
977
+ { deep: true }
978
+ );
979
+ const switchHuman = (humanId) => {
980
+ const next = resolveHuman(humanId);
981
+ if (!next) return;
982
+ abortTTS();
983
+ stop();
984
+ timestamp.value = null;
985
+ frameIndex.value = 1;
986
+ currentHuman.value = next;
987
+ loadFrames();
988
+ waitFramesLoaded(() => {
989
+ draw(frames.value.standby[0]);
990
+ emit("changed", currentHuman.value);
991
+ });
992
+ };
868
993
  __expose({
869
994
  standby,
870
995
  speak,
@@ -873,13 +998,9 @@ const _sfc_main = {
873
998
  resume,
874
999
  playTTS,
875
1000
  startRecord,
876
- stopRecord
1001
+ stopRecord,
1002
+ switchHuman
877
1003
  });
878
- vue.watch(() => props.humans, (val) => {
879
- if (val && val.length > 0 && !currentHuman.value) {
880
- init();
881
- }
882
- }, { deep: true });
883
1004
  vue.onMounted(() => {
884
1005
  initRecorder();
885
1006
  init();
@@ -905,7 +1026,7 @@ const _sfc_main = {
905
1026
  };
906
1027
  }
907
1028
  };
908
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9379171c"]]);
1029
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-eb00f641"]]);
909
1030
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
910
1031
  const components = [DemoButton, DemoInput, DigitalHuman];
911
1032
  const install = (app) => {
@@ -391,15 +391,89 @@ const TTS_MAX_RETRIES = 5;
391
391
  const _sfc_main = {
392
392
  __name: "index",
393
393
  props: {
394
- humans: {
395
- type: Array,
396
- default: () => []
394
+ human: {
395
+ type: Object,
396
+ default: null
397
397
  }
398
398
  },
399
399
  emits: ["loaded", "changed", "paused", "resumed", "audio-ready"],
400
400
  setup(__props, { expose: __expose, emit: __emit }) {
401
401
  const TTS_URL = "http://localhost:6039";
402
402
  const ASR_URL = "http://localhost:6039";
403
+ const HUMAN_PRESETS = {
404
+ 1: {
405
+ humanId: 1,
406
+ title: "西装动漫男",
407
+ // TODO: 替换为真实资源
408
+ folder: "http://192.168.3.107:8088/img/xiangyu-green-suit",
409
+ folder_mobile: "http://192.168.3.107:8088/img/xiangyu-green-suit-header",
410
+ standby: { interval: 100, standbyNum: 11 },
411
+ speak: { interval: 30, speakNum: 0 },
412
+ humanType: 1,
413
+ timbre: 1
414
+ },
415
+ 2: {
416
+ humanId: 2,
417
+ title: "西装动漫女",
418
+ folder: "http://192.168.3.107:8088/img/anime-woman",
419
+ folder_mobile: "http://192.168.3.107:8088/img/anime-woman-header",
420
+ standby: { interval: 100, standbyNum: 1 },
421
+ speak: { interval: 30, speakNum: 75 },
422
+ humanType: 1,
423
+ timbre: 3
424
+ },
425
+ 3: {
426
+ humanId: 3,
427
+ title: "真人形象男",
428
+ folder: "http://192.168.3.107:8088/img/real-man",
429
+ folder_mobile: "http://192.168.3.107:8088/img/real-man-header",
430
+ standby: { interval: 100, standbyNum: 1 },
431
+ speak: { interval: 30, speakNum: 139 },
432
+ humanType: 1,
433
+ timbre: 47
434
+ },
435
+ 4: {
436
+ humanId: 4,
437
+ title: "真人形象女",
438
+ folder: "http://192.168.3.107:8088/img/real-woman",
439
+ folder_mobile: "http://192.168.3.107:8088/img/real-woman-header",
440
+ standby: { interval: 100, standbyNum: 2 },
441
+ speak: { interval: 40, speakNum: 139 },
442
+ humanType: 1,
443
+ timbre: 34
444
+ },
445
+ 5: {
446
+ humanId: 5,
447
+ title: "Q版形象",
448
+ folder: "http://192.168.3.107:8088/img/xiangyu-q",
449
+ folder_mobile: "http://192.168.3.107:8088/img/xiangyu-q-header",
450
+ standby: { interval: 100, standbyNum: 28 },
451
+ speak: { interval: 30, speakNum: 118 },
452
+ humanType: 1,
453
+ timbre: 34
454
+ }
455
+ };
456
+ const isMobile = () => {
457
+ if (typeof navigator === "undefined") return false;
458
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|HarmonyOS/i.test(navigator.userAgent);
459
+ };
460
+ const resolveFolder = (human) => {
461
+ const finalType = human.humanType || (isMobile() ? 2 : 1);
462
+ if (finalType === 2 && human.folder_mobile) return human.folder_mobile;
463
+ return human.folder;
464
+ };
465
+ const resolveHuman = (input) => {
466
+ if (input == null) return null;
467
+ const humanId = typeof input === "number" ? input : input.humanId;
468
+ const preset = HUMAN_PRESETS[humanId];
469
+ if (!preset) {
470
+ console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(HUMAN_PRESETS));
471
+ return null;
472
+ }
473
+ const merged = { ...preset, ...typeof input === "object" ? input : {} };
474
+ merged.folder = resolveFolder(merged);
475
+ return merged;
476
+ };
403
477
  const props = __props;
404
478
  const emit = __emit;
405
479
  const containerRef = ref(null);
@@ -421,27 +495,38 @@ const _sfc_main = {
421
495
  standbyNum: currentHuman.value.standby.standbyNum,
422
496
  speakNum: currentHuman.value.speak.speakNum
423
497
  };
424
- for (let i = 1; i <= standbyNum; i++) {
498
+ const makeImage = (url) => {
425
499
  const image = new Image();
426
- image.src = encodeURI(`${folder}/standby/standby (${i}).png`);
427
- frames.value.standby.push(image);
500
+ image.src = encodeURI(url);
501
+ image.onload = () => {
502
+ image.__broken = false;
503
+ };
504
+ image.onerror = () => {
505
+ image.__broken = true;
506
+ console.warn("[DigitalHuman] 帧加载失败:", url);
507
+ };
508
+ return image;
509
+ };
510
+ for (let i = 1; i <= standbyNum; i++) {
511
+ frames.value.standby.push(makeImage(`${folder}/standby/standby (${i}).png`));
512
+ }
513
+ if (speakNum <= 0) {
514
+ console.warn("[DigitalHuman] 当前形象 speakNum=0,说话动画暂用待机帧替代");
515
+ frames.value.speakStart = [...frames.value.standby];
516
+ frames.value.speakLoop = [...frames.value.standby];
517
+ frames.value.speakEnd = [...frames.value.standby];
518
+ return;
428
519
  }
429
520
  const startEnd = Math.floor(speakNum * 0.6);
430
521
  const loopEnd = Math.floor(speakNum * 0.9);
431
522
  for (let i = 1; i <= startEnd; i++) {
432
- const image = new Image();
433
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
434
- frames.value.speakStart.push(image);
523
+ frames.value.speakStart.push(makeImage(`${folder}/speak/speak (${i}).png`));
435
524
  }
436
525
  for (let i = startEnd; i <= loopEnd; i++) {
437
- const image = new Image();
438
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
439
- frames.value.speakLoop.push(image);
526
+ frames.value.speakLoop.push(makeImage(`${folder}/speak/speak (${i}).png`));
440
527
  }
441
528
  for (let i = loopEnd; i <= speakNum; i++) {
442
- const image = new Image();
443
- image.src = encodeURI(`${folder}/speak/speak (${i}).png`);
444
- frames.value.speakEnd.push(image);
529
+ frames.value.speakEnd.push(makeImage(`${folder}/speak/speak (${i}).png`));
445
530
  }
446
531
  };
447
532
  const draw = (image) => {
@@ -602,20 +687,38 @@ const _sfc_main = {
602
687
  };
603
688
  allFrames.forEach((img) => {
604
689
  if (img.complete) {
690
+ if (!img.naturalWidth) img.__broken = true;
605
691
  checkLoaded();
606
692
  } else {
607
- img.onload = checkLoaded;
608
- img.onerror = checkLoaded;
693
+ img.onload = () => {
694
+ img.__broken = false;
695
+ checkLoaded();
696
+ };
697
+ img.onerror = () => {
698
+ img.__broken = true;
699
+ checkLoaded();
700
+ };
609
701
  }
610
702
  });
611
703
  };
612
704
  const init = () => {
613
705
  nextTick(() => {
706
+ var _a, _b;
614
707
  if (!containerRef.value || !canvasRef.value) return;
615
- if (!props.humans || props.humans.length === 0) return;
616
- if (!currentHuman.value) {
617
- currentHuman.value = props.humans[0];
708
+ const rawInput = props.human;
709
+ if (!rawInput) return;
710
+ const human = resolveHuman(rawInput);
711
+ if (!human) return;
712
+ const prevType = (_a = currentHuman.value) == null ? void 0 : _a.humanType;
713
+ if (((_b = currentHuman.value) == null ? void 0 : _b.humanId) === human.humanId && prevType === human.humanType) {
714
+ Object.assign(currentHuman.value, human);
715
+ return;
618
716
  }
717
+ abortTTS();
718
+ stop();
719
+ timestamp.value = null;
720
+ frameIndex.value = 1;
721
+ currentHuman.value = human;
619
722
  const canvas = canvasRef.value;
620
723
  const dpr = window.devicePixelRatio || 1;
621
724
  const width = containerRef.value.clientWidth;
@@ -692,6 +795,7 @@ const _sfc_main = {
692
795
  return pcmToWavBlob(buf);
693
796
  };
694
797
  const requestTTSAudio = async (text, signal) => {
798
+ var _a;
695
799
  let lastError = null;
696
800
  for (let attempt = 1; attempt <= TTS_MAX_RETRIES; attempt++) {
697
801
  if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
@@ -702,7 +806,7 @@ const _sfc_main = {
702
806
  clientid: VOICE_CLIENT_ID,
703
807
  "tenant-id": VOICE_TENANT_ID
704
808
  },
705
- body: JSON.stringify({ text, user: TTS_USER, timbre: 47 }),
809
+ body: JSON.stringify({ text, user: TTS_USER, timbre: ((_a = currentHuman.value) == null ? void 0 : _a.timbre) ?? 1 }),
706
810
  signal
707
811
  });
708
812
  if (!res.ok) {
@@ -863,6 +967,27 @@ const _sfc_main = {
863
967
  });
864
968
  }
865
969
  };
970
+ watch(
971
+ () => [props.human],
972
+ () => {
973
+ init();
974
+ },
975
+ { deep: true }
976
+ );
977
+ const switchHuman = (humanId) => {
978
+ const next = resolveHuman(humanId);
979
+ if (!next) return;
980
+ abortTTS();
981
+ stop();
982
+ timestamp.value = null;
983
+ frameIndex.value = 1;
984
+ currentHuman.value = next;
985
+ loadFrames();
986
+ waitFramesLoaded(() => {
987
+ draw(frames.value.standby[0]);
988
+ emit("changed", currentHuman.value);
989
+ });
990
+ };
866
991
  __expose({
867
992
  standby,
868
993
  speak,
@@ -871,13 +996,9 @@ const _sfc_main = {
871
996
  resume,
872
997
  playTTS,
873
998
  startRecord,
874
- stopRecord
999
+ stopRecord,
1000
+ switchHuman
875
1001
  });
876
- watch(() => props.humans, (val) => {
877
- if (val && val.length > 0 && !currentHuman.value) {
878
- init();
879
- }
880
- }, { deep: true });
881
1002
  onMounted(() => {
882
1003
  initRecorder();
883
1004
  init();
@@ -903,7 +1024,7 @@ const _sfc_main = {
903
1024
  };
904
1025
  }
905
1026
  };
906
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9379171c"]]);
1027
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-eb00f641"]]);
907
1028
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
908
1029
  const components = [DemoButton, DemoInput, DigitalHuman];
909
1030
  const install = (app) => {
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-9379171c]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-9379171c]{max-width:100%;max-height:100%}
1
+ .demo-btn[data-v-9a48516a]{padding:6px 14px;border-radius:4px;border:none;cursor:pointer;font-size:14px}.primary[data-v-9a48516a]{background:#409eff;color:#fff}.success[data-v-9a48516a]{background:#67c23a;color:#fff}.warning[data-v-9a48516a]{background:#e6a23c;color:#fff}.demo-input-wrap[data-v-28a3493c]{display:inline-flex;align-items:center;border:1px solid #dcdcdc;border-radius:4px;padding:0 10px}.demo-input-wrap[data-v-28a3493c]:focus-within{border-color:#409eff}.demo-input[data-v-28a3493c]{border:none;outline:none;padding:8px 6px;flex:1}.prefix[data-v-28a3493c],.suffix[data-v-28a3493c]{color:#909399}.digital-human[data-v-eb00f641]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-eb00f641]{max-width:100%;max-height:100%}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruiyun-human",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "type": "module",
5
5
  "description": "数字人初步实现",
6
6
  "main": "./dist/ruiyun-human.cjs.js",