ruiyun-human 1.0.18 → 1.0.20

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.
@@ -388,7 +388,6 @@ const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
388
388
  const VOICE_CLIENT_ID = "e5cd2b";
389
389
  const VOICE_TENANT_ID = "000000";
390
390
  const TTS_USER = "web-test-user";
391
- const szrUrl = `https://sqsyky.xzspj.suqian.gov.cn:8443/szrimage/`;
392
391
  const TTS_PCM_SAMPLE_RATE = 16e3;
393
392
  const TTS_MAX_RETRIES = 5;
394
393
  const _sfc_main = {
@@ -406,15 +405,15 @@ const _sfc_main = {
406
405
  emits: ["loaded", "changed", "paused", "resumed", "audio-ready"],
407
406
  setup(__props, { expose: __expose, emit: __emit }) {
408
407
  const props = __props;
409
- const TTS_URL = `${props.szraddress}`;
410
- const ASR_URL = `${props.szraddress}`;
411
- const HUMAN_PRESETS = {
408
+ const TTS_URL = vue.computed(() => props.szraddress || "");
409
+ const ASR_URL = vue.computed(() => props.szraddress || "");
410
+ const szrUrl = vue.computed(() => `${props.szraddress || ""}/szrimage`);
411
+ const buildPresets = (rootUrl) => ({
412
412
  1: {
413
413
  humanId: 1,
414
- title: "西装动漫男",
415
- // TODO: 替换为真实资源
416
- folder: `${szrUrl}/xiangyu-green-suit`,
417
- folder_mobile: `${szrUrl}/xiangyu-green-suit-header`,
414
+ title: "羽哥",
415
+ folder: `${rootUrl}/xiangyu-green-suit`,
416
+ folder_mobile: `${rootUrl}/xiangyu-green-suit-header`,
418
417
  standby: { interval: 100, standbyNum: 11 },
419
418
  speak: { interval: 30, speakNum: 274 },
420
419
  humanType: 1,
@@ -422,9 +421,9 @@ const _sfc_main = {
422
421
  },
423
422
  2: {
424
423
  humanId: 2,
425
- title: "西装动漫女",
426
- folder: `${szrUrl}/anime-woman`,
427
- folder_mobile: `${szrUrl}/anime-woman-header`,
424
+ title: "虞姐",
425
+ folder: `${rootUrl}/anime-woman`,
426
+ folder_mobile: `${rootUrl}/anime-woman-header`,
428
427
  standby: { interval: 100, standbyNum: 1 },
429
428
  speak: { interval: 30, speakNum: 75 },
430
429
  humanType: 1,
@@ -432,9 +431,9 @@ const _sfc_main = {
432
431
  },
433
432
  3: {
434
433
  humanId: 3,
435
- title: "真人形象男",
436
- folder: `${szrUrl}/real-man`,
437
- folder_mobile: `${szrUrl}/real-man-header`,
434
+ title: "羽哥",
435
+ folder: `${rootUrl}/real-man`,
436
+ folder_mobile: `${rootUrl}/real-man-header`,
438
437
  standby: { interval: 100, standbyNum: 1 },
439
438
  speak: { interval: 30, speakNum: 139 },
440
439
  humanType: 1,
@@ -442,9 +441,9 @@ const _sfc_main = {
442
441
  },
443
442
  4: {
444
443
  humanId: 4,
445
- title: "真人形象女",
446
- folder: `${szrUrl}/real-woman`,
447
- folder_mobile: `${szrUrl}/real-woman-header`,
444
+ title: "虞姐",
445
+ folder: `${rootUrl}/real-woman`,
446
+ folder_mobile: `${rootUrl}/real-woman-header`,
448
447
  standby: { interval: 100, standbyNum: 2 },
449
448
  speak: { interval: 40, speakNum: 139 },
450
449
  humanType: 1,
@@ -452,15 +451,15 @@ const _sfc_main = {
452
451
  },
453
452
  5: {
454
453
  humanId: 5,
455
- title: "Q版形象",
456
- folder: `${szrUrl}/xiangyu-q`,
457
- folder_mobile: `${szrUrl}/xiangyu-q-header`,
454
+ title: "羽哥",
455
+ folder: `${rootUrl}/xiangyu-q`,
456
+ folder_mobile: `${rootUrl}/xiangyu-q-header`,
458
457
  standby: { interval: 100, standbyNum: 23 },
459
458
  speak: { interval: 30, speakNum: 118 },
460
459
  humanType: 1,
461
460
  timbre: 47
462
461
  }
463
- };
462
+ });
464
463
  const isMobile = () => {
465
464
  if (typeof navigator === "undefined") return false;
466
465
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|HarmonyOS/i.test(navigator.userAgent);
@@ -473,9 +472,9 @@ const _sfc_main = {
473
472
  const resolveHuman = (input) => {
474
473
  if (input == null) return null;
475
474
  const humanId = typeof input === "number" ? input : input.humanId;
476
- const preset = HUMAN_PRESETS[humanId];
475
+ const preset = buildPresets(szrUrl.value)[humanId];
477
476
  if (!preset) {
478
- console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(HUMAN_PRESETS));
477
+ console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(buildPresets(szrUrl.value)));
479
478
  return null;
480
479
  }
481
480
  const merged = { ...preset, ...typeof input === "object" ? input : {} };
@@ -502,15 +501,23 @@ const _sfc_main = {
502
501
  standbyNum: currentHuman.value.standby.standbyNum,
503
502
  speakNum: currentHuman.value.speak.speakNum
504
503
  };
504
+ console.log("[DigitalHuman] 开始加载帧:", {
505
+ folder,
506
+ standbyNum,
507
+ speakNum,
508
+ humanId: currentHuman.value.humanId,
509
+ title: currentHuman.value.title
510
+ });
505
511
  const makeImage = (url) => {
506
512
  const image = new Image();
507
513
  image.src = encodeURI(url);
508
514
  image.onload = () => {
509
515
  image.__broken = false;
516
+ console.log("[DigitalHuman] ✅ 帧加载成功:", url);
510
517
  };
511
518
  image.onerror = () => {
512
519
  image.__broken = true;
513
- console.warn("[DigitalHuman] 帧加载失败:", url);
520
+ console.warn("[DigitalHuman] 帧加载失败:", url);
514
521
  };
515
522
  return image;
516
523
  };
@@ -731,10 +738,18 @@ const _sfc_main = {
731
738
  const dpr = window.devicePixelRatio || 1;
732
739
  const width = containerRef.value.clientWidth;
733
740
  const height = containerRef.value.clientHeight;
734
- canvas.width = width * dpr;
735
- canvas.height = height * dpr;
736
- canvas.style.width = width + "px";
737
- canvas.style.height = height + "px";
741
+ if (!width || !height) {
742
+ console.warn('[DigitalHuman] 容器宽高为 0!组件根元素是 width:100%;height:100%,需要父容器提供宽高(如 style="width:500px;height:500px" class="w-full h-screen")。已自动设置最小尺寸 300×300 作为兜底');
743
+ canvas.style.width = "300px";
744
+ canvas.style.height = "300px";
745
+ canvas.width = 300 * dpr;
746
+ canvas.height = 300 * dpr;
747
+ } else {
748
+ canvas.width = width * dpr;
749
+ canvas.height = height * dpr;
750
+ canvas.style.width = width + "px";
751
+ canvas.style.height = height + "px";
752
+ }
738
753
  ctx.value = canvas.getContext("2d");
739
754
  loadFrames();
740
755
  waitFramesLoaded(() => {
@@ -808,7 +823,7 @@ const _sfc_main = {
808
823
  for (let attempt = 1; attempt <= TTS_MAX_RETRIES; attempt++) {
809
824
  if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
810
825
  console.log("[TTS] 提交合成请求1111:", ((_a = currentHuman.value) == null ? void 0 : _a.timbre) ?? 1);
811
- const res = await fetch(`${TTS_URL}/web/voice/jd/text-to-speech`, {
826
+ const res = await fetch(`${TTS_URL.value}/web/voice/jd/text-to-speech`, {
812
827
  method: "POST",
813
828
  headers: {
814
829
  "Content-Type": "application/json",
@@ -946,7 +961,7 @@ const _sfc_main = {
946
961
  domain: "general",
947
962
  sampleRate: "16000"
948
963
  });
949
- const url = `${ASR_URL}/web/voice/jd/speech-to-text?${params}`;
964
+ const url = `${ASR_URL.value}/web/voice/jd/speech-to-text?${params}`;
950
965
  try {
951
966
  const res = await fetch(url, {
952
967
  method: "POST",
@@ -981,7 +996,7 @@ const _sfc_main = {
981
996
  }
982
997
  };
983
998
  vue.watch(
984
- () => [props.human],
999
+ () => props.human,
985
1000
  () => {
986
1001
  init();
987
1002
  },
@@ -1038,7 +1053,7 @@ const _sfc_main = {
1038
1053
  };
1039
1054
  }
1040
1055
  };
1041
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-60e083ad"]]);
1056
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-a40d6a6d"]]);
1042
1057
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
1043
1058
  const components = [DemoButton, DemoInput, DigitalHuman];
1044
1059
  const install = (app) => {
@@ -1,4 +1,4 @@
1
- import { openBlock, createElementBlock, normalizeClass, renderSlot, createCommentVNode, createElementVNode, ref, watch, onMounted, onUnmounted, nextTick } from "vue";
1
+ import { openBlock, createElementBlock, normalizeClass, renderSlot, createCommentVNode, createElementVNode, computed, ref, watch, onMounted, onUnmounted, nextTick } from "vue";
2
2
  const _export_sfc = (sfc, props) => {
3
3
  const target = sfc.__vccOpts || sfc;
4
4
  for (const [key, val] of props) {
@@ -386,7 +386,6 @@ const Recorder = /* @__PURE__ */ getDefaultExportFromCjs(jsAudioRecorder);
386
386
  const VOICE_CLIENT_ID = "e5cd2b";
387
387
  const VOICE_TENANT_ID = "000000";
388
388
  const TTS_USER = "web-test-user";
389
- const szrUrl = `https://sqsyky.xzspj.suqian.gov.cn:8443/szrimage/`;
390
389
  const TTS_PCM_SAMPLE_RATE = 16e3;
391
390
  const TTS_MAX_RETRIES = 5;
392
391
  const _sfc_main = {
@@ -404,15 +403,15 @@ const _sfc_main = {
404
403
  emits: ["loaded", "changed", "paused", "resumed", "audio-ready"],
405
404
  setup(__props, { expose: __expose, emit: __emit }) {
406
405
  const props = __props;
407
- const TTS_URL = `${props.szraddress}`;
408
- const ASR_URL = `${props.szraddress}`;
409
- const HUMAN_PRESETS = {
406
+ const TTS_URL = computed(() => props.szraddress || "");
407
+ const ASR_URL = computed(() => props.szraddress || "");
408
+ const szrUrl = computed(() => `${props.szraddress || ""}/szrimage`);
409
+ const buildPresets = (rootUrl) => ({
410
410
  1: {
411
411
  humanId: 1,
412
- title: "西装动漫男",
413
- // TODO: 替换为真实资源
414
- folder: `${szrUrl}/xiangyu-green-suit`,
415
- folder_mobile: `${szrUrl}/xiangyu-green-suit-header`,
412
+ title: "羽哥",
413
+ folder: `${rootUrl}/xiangyu-green-suit`,
414
+ folder_mobile: `${rootUrl}/xiangyu-green-suit-header`,
416
415
  standby: { interval: 100, standbyNum: 11 },
417
416
  speak: { interval: 30, speakNum: 274 },
418
417
  humanType: 1,
@@ -420,9 +419,9 @@ const _sfc_main = {
420
419
  },
421
420
  2: {
422
421
  humanId: 2,
423
- title: "西装动漫女",
424
- folder: `${szrUrl}/anime-woman`,
425
- folder_mobile: `${szrUrl}/anime-woman-header`,
422
+ title: "虞姐",
423
+ folder: `${rootUrl}/anime-woman`,
424
+ folder_mobile: `${rootUrl}/anime-woman-header`,
426
425
  standby: { interval: 100, standbyNum: 1 },
427
426
  speak: { interval: 30, speakNum: 75 },
428
427
  humanType: 1,
@@ -430,9 +429,9 @@ const _sfc_main = {
430
429
  },
431
430
  3: {
432
431
  humanId: 3,
433
- title: "真人形象男",
434
- folder: `${szrUrl}/real-man`,
435
- folder_mobile: `${szrUrl}/real-man-header`,
432
+ title: "羽哥",
433
+ folder: `${rootUrl}/real-man`,
434
+ folder_mobile: `${rootUrl}/real-man-header`,
436
435
  standby: { interval: 100, standbyNum: 1 },
437
436
  speak: { interval: 30, speakNum: 139 },
438
437
  humanType: 1,
@@ -440,9 +439,9 @@ const _sfc_main = {
440
439
  },
441
440
  4: {
442
441
  humanId: 4,
443
- title: "真人形象女",
444
- folder: `${szrUrl}/real-woman`,
445
- folder_mobile: `${szrUrl}/real-woman-header`,
442
+ title: "虞姐",
443
+ folder: `${rootUrl}/real-woman`,
444
+ folder_mobile: `${rootUrl}/real-woman-header`,
446
445
  standby: { interval: 100, standbyNum: 2 },
447
446
  speak: { interval: 40, speakNum: 139 },
448
447
  humanType: 1,
@@ -450,15 +449,15 @@ const _sfc_main = {
450
449
  },
451
450
  5: {
452
451
  humanId: 5,
453
- title: "Q版形象",
454
- folder: `${szrUrl}/xiangyu-q`,
455
- folder_mobile: `${szrUrl}/xiangyu-q-header`,
452
+ title: "羽哥",
453
+ folder: `${rootUrl}/xiangyu-q`,
454
+ folder_mobile: `${rootUrl}/xiangyu-q-header`,
456
455
  standby: { interval: 100, standbyNum: 23 },
457
456
  speak: { interval: 30, speakNum: 118 },
458
457
  humanType: 1,
459
458
  timbre: 47
460
459
  }
461
- };
460
+ });
462
461
  const isMobile = () => {
463
462
  if (typeof navigator === "undefined") return false;
464
463
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|HarmonyOS/i.test(navigator.userAgent);
@@ -471,9 +470,9 @@ const _sfc_main = {
471
470
  const resolveHuman = (input) => {
472
471
  if (input == null) return null;
473
472
  const humanId = typeof input === "number" ? input : input.humanId;
474
- const preset = HUMAN_PRESETS[humanId];
473
+ const preset = buildPresets(szrUrl.value)[humanId];
475
474
  if (!preset) {
476
- console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(HUMAN_PRESETS));
475
+ console.error("[DigitalHuman] 未找到 humanId=", humanId, "的预设形象,可用 id:", Object.keys(buildPresets(szrUrl.value)));
477
476
  return null;
478
477
  }
479
478
  const merged = { ...preset, ...typeof input === "object" ? input : {} };
@@ -500,15 +499,23 @@ const _sfc_main = {
500
499
  standbyNum: currentHuman.value.standby.standbyNum,
501
500
  speakNum: currentHuman.value.speak.speakNum
502
501
  };
502
+ console.log("[DigitalHuman] 开始加载帧:", {
503
+ folder,
504
+ standbyNum,
505
+ speakNum,
506
+ humanId: currentHuman.value.humanId,
507
+ title: currentHuman.value.title
508
+ });
503
509
  const makeImage = (url) => {
504
510
  const image = new Image();
505
511
  image.src = encodeURI(url);
506
512
  image.onload = () => {
507
513
  image.__broken = false;
514
+ console.log("[DigitalHuman] ✅ 帧加载成功:", url);
508
515
  };
509
516
  image.onerror = () => {
510
517
  image.__broken = true;
511
- console.warn("[DigitalHuman] 帧加载失败:", url);
518
+ console.warn("[DigitalHuman] 帧加载失败:", url);
512
519
  };
513
520
  return image;
514
521
  };
@@ -729,10 +736,18 @@ const _sfc_main = {
729
736
  const dpr = window.devicePixelRatio || 1;
730
737
  const width = containerRef.value.clientWidth;
731
738
  const height = containerRef.value.clientHeight;
732
- canvas.width = width * dpr;
733
- canvas.height = height * dpr;
734
- canvas.style.width = width + "px";
735
- canvas.style.height = height + "px";
739
+ if (!width || !height) {
740
+ console.warn('[DigitalHuman] 容器宽高为 0!组件根元素是 width:100%;height:100%,需要父容器提供宽高(如 style="width:500px;height:500px" class="w-full h-screen")。已自动设置最小尺寸 300×300 作为兜底');
741
+ canvas.style.width = "300px";
742
+ canvas.style.height = "300px";
743
+ canvas.width = 300 * dpr;
744
+ canvas.height = 300 * dpr;
745
+ } else {
746
+ canvas.width = width * dpr;
747
+ canvas.height = height * dpr;
748
+ canvas.style.width = width + "px";
749
+ canvas.style.height = height + "px";
750
+ }
736
751
  ctx.value = canvas.getContext("2d");
737
752
  loadFrames();
738
753
  waitFramesLoaded(() => {
@@ -806,7 +821,7 @@ const _sfc_main = {
806
821
  for (let attempt = 1; attempt <= TTS_MAX_RETRIES; attempt++) {
807
822
  if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
808
823
  console.log("[TTS] 提交合成请求1111:", ((_a = currentHuman.value) == null ? void 0 : _a.timbre) ?? 1);
809
- const res = await fetch(`${TTS_URL}/web/voice/jd/text-to-speech`, {
824
+ const res = await fetch(`${TTS_URL.value}/web/voice/jd/text-to-speech`, {
810
825
  method: "POST",
811
826
  headers: {
812
827
  "Content-Type": "application/json",
@@ -944,7 +959,7 @@ const _sfc_main = {
944
959
  domain: "general",
945
960
  sampleRate: "16000"
946
961
  });
947
- const url = `${ASR_URL}/web/voice/jd/speech-to-text?${params}`;
962
+ const url = `${ASR_URL.value}/web/voice/jd/speech-to-text?${params}`;
948
963
  try {
949
964
  const res = await fetch(url, {
950
965
  method: "POST",
@@ -979,7 +994,7 @@ const _sfc_main = {
979
994
  }
980
995
  };
981
996
  watch(
982
- () => [props.human],
997
+ () => props.human,
983
998
  () => {
984
999
  init();
985
1000
  },
@@ -1036,7 +1051,7 @@ const _sfc_main = {
1036
1051
  };
1037
1052
  }
1038
1053
  };
1039
- const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-60e083ad"]]);
1054
+ const DigitalHuman = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-a40d6a6d"]]);
1040
1055
  DigitalHuman.install = (app) => app.component("DigitalHuman", DigitalHuman);
1041
1056
  const components = [DemoButton, DemoInput, DigitalHuman];
1042
1057
  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-60e083ad]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-60e083ad]{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-a40d6a6d]{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.digital-human canvas[data-v-a40d6a6d]{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.18",
3
+ "version": "1.0.20",
4
4
  "type": "module",
5
5
  "description": "数字人初步实现",
6
6
  "main": "./dist/ruiyun-human.cjs.js",