@yh-ui/components 0.1.12 → 0.1.15

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.
Files changed (90) hide show
  1. package/dist/ai-artifacts/src/ai-artifacts.d.ts +1 -1
  2. package/dist/ai-artifacts/src/ai-artifacts.d.vue.ts +18 -1
  3. package/dist/ai-artifacts/src/ai-artifacts.vue +30 -2
  4. package/dist/ai-artifacts/src/ai-artifacts.vue.d.ts +18 -1
  5. package/dist/ai-editor-sender/src/ai-editor-sender.d.vue.ts +2 -2
  6. package/dist/ai-editor-sender/src/ai-editor-sender.vue.d.ts +2 -2
  7. package/dist/ai-mention/__tests__/ai-mention.ssr.test.cjs +38 -0
  8. package/dist/ai-mention/__tests__/ai-mention.ssr.test.d.ts +1 -0
  9. package/dist/ai-mention/__tests__/ai-mention.ssr.test.mjs +37 -0
  10. package/dist/ai-mention/__tests__/ai-mention.test.cjs +113 -0
  11. package/dist/ai-mention/__tests__/ai-mention.test.d.ts +1 -0
  12. package/dist/ai-mention/__tests__/ai-mention.test.mjs +84 -0
  13. package/dist/ai-mention/index.cjs +26 -0
  14. package/dist/ai-mention/index.d.ts +29 -0
  15. package/dist/ai-mention/index.mjs +5 -0
  16. package/dist/ai-mention/src/ai-mention.cjs +95 -0
  17. package/dist/ai-mention/src/ai-mention.css +545 -0
  18. package/dist/ai-mention/src/ai-mention.d.ts +99 -0
  19. package/dist/ai-mention/src/ai-mention.d.vue.ts +68 -0
  20. package/dist/ai-mention/src/ai-mention.mjs +89 -0
  21. package/dist/ai-mention/src/ai-mention.vue +650 -0
  22. package/dist/ai-mention/src/ai-mention.vue.d.ts +68 -0
  23. package/dist/ai-sender/__tests__/ai-sender.test.cjs +17 -10
  24. package/dist/ai-sender/__tests__/ai-sender.test.mjs +17 -10
  25. package/dist/ai-sender/src/ai-sender.cjs +7 -0
  26. package/dist/ai-sender/src/ai-sender.d.ts +7 -0
  27. package/dist/ai-sender/src/ai-sender.d.vue.ts +8 -3
  28. package/dist/ai-sender/src/ai-sender.mjs +7 -0
  29. package/dist/ai-sender/src/ai-sender.vue +25 -18
  30. package/dist/ai-sender/src/ai-sender.vue.d.ts +8 -3
  31. package/dist/ai-sources/src/ai-sources.d.vue.ts +2 -2
  32. package/dist/ai-sources/src/ai-sources.vue +41 -57
  33. package/dist/ai-sources/src/ai-sources.vue.d.ts +2 -2
  34. package/dist/ai-voice-trigger/__tests__/ai-voice-trigger.test.cjs +19 -2
  35. package/dist/ai-voice-trigger/__tests__/ai-voice-trigger.test.mjs +12 -2
  36. package/dist/ai-voice-trigger/src/ai-voice-trigger.cjs +32 -0
  37. package/dist/ai-voice-trigger/src/ai-voice-trigger.css +111 -12
  38. package/dist/ai-voice-trigger/src/ai-voice-trigger.d.ts +32 -0
  39. package/dist/ai-voice-trigger/src/ai-voice-trigger.d.vue.ts +22 -2
  40. package/dist/ai-voice-trigger/src/ai-voice-trigger.mjs +32 -0
  41. package/dist/ai-voice-trigger/src/ai-voice-trigger.vue +201 -50
  42. package/dist/ai-voice-trigger/src/ai-voice-trigger.vue.d.ts +22 -2
  43. package/dist/alert/src/alert.d.vue.ts +1 -1
  44. package/dist/alert/src/alert.vue.d.ts +1 -1
  45. package/dist/autocomplete/src/autocomplete.d.vue.ts +2 -2
  46. package/dist/autocomplete/src/autocomplete.vue.d.ts +2 -2
  47. package/dist/calendar/src/calendar.d.vue.ts +1 -1
  48. package/dist/calendar/src/calendar.vue.d.ts +1 -1
  49. package/dist/date-picker/src/date-picker.d.vue.ts +2 -2
  50. package/dist/date-picker/src/date-picker.vue.d.ts +2 -2
  51. package/dist/dialog/src/dialog.d.vue.ts +8 -8
  52. package/dist/dialog/src/dialog.vue.d.ts +8 -8
  53. package/dist/drawer/index.d.ts +42 -1
  54. package/dist/drawer/src/drawer.d.vue.ts +4 -4
  55. package/dist/drawer/src/drawer.vue.d.ts +4 -4
  56. package/dist/icon/src/icons/index.cjs +7 -2
  57. package/dist/icon/src/icons/index.d.ts +1 -0
  58. package/dist/icon/src/icons/index.mjs +7 -1
  59. package/dist/index.cjs +13 -1
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.mjs +4 -1
  62. package/dist/infinite-scroll/src/infinite-scroll.d.vue.ts +1 -1
  63. package/dist/infinite-scroll/src/infinite-scroll.vue.d.ts +1 -1
  64. package/dist/input/index.d.ts +3 -3
  65. package/dist/input/src/input.d.vue.ts +1 -1
  66. package/dist/input/src/input.vue.d.ts +1 -1
  67. package/dist/input-number/index.d.ts +3 -3
  68. package/dist/input-number/src/input-number.d.vue.ts +1 -1
  69. package/dist/input-number/src/input-number.vue.d.ts +1 -1
  70. package/dist/input-tag/src/input-tag.d.vue.ts +2 -2
  71. package/dist/input-tag/src/input-tag.vue.d.ts +2 -2
  72. package/dist/mention/index.d.ts +181 -1
  73. package/dist/mention/src/mention.d.vue.ts +5 -5
  74. package/dist/mention/src/mention.vue +6 -2
  75. package/dist/mention/src/mention.vue.d.ts +5 -5
  76. package/dist/message/src/message.d.vue.ts +1 -1
  77. package/dist/message/src/message.vue.d.ts +1 -1
  78. package/dist/notification/src/notification.d.vue.ts +1 -1
  79. package/dist/notification/src/notification.vue.d.ts +1 -1
  80. package/dist/select/src/select.d.vue.ts +3 -3
  81. package/dist/select/src/select.vue.d.ts +3 -3
  82. package/dist/skeleton/src/skeleton.d.vue.ts +1 -1
  83. package/dist/skeleton/src/skeleton.vue.d.ts +1 -1
  84. package/dist/table/src/table-column.d.vue.ts +1 -1
  85. package/dist/table/src/table-column.vue.d.ts +1 -1
  86. package/dist/table/src/table.d.vue.ts +2 -2
  87. package/dist/table/src/table.vue.d.ts +2 -2
  88. package/dist/time-picker/src/time-picker.d.vue.ts +1 -1
  89. package/dist/time-picker/src/time-picker.vue.d.ts +1 -1
  90. package/package.json +5 -5
@@ -18,9 +18,9 @@ describe("YhAiVoiceTrigger", () => {
18
18
  });
19
19
  it("should apply recording class when recording is true", () => {
20
20
  const wrapper = mount(AiVoiceTrigger, {
21
- props: { recording: true }
21
+ props: { recording: true, teleport: false }
22
22
  });
23
- expect(wrapper.classes()).toContain("is-recording");
23
+ expect(wrapper.find(".yh-ai-voice-trigger").classes()).toContain("is-recording");
24
24
  expect(wrapper.find(".yh-ai-voice-trigger__visualizer").exists()).toBe(true);
25
25
  });
26
26
  it("should emit start and update:recording on click when initially false", async () => {
@@ -90,4 +90,14 @@ describe("YhAiVoiceTrigger", () => {
90
90
  });
91
91
  expect(wrapper.find(".yh-ai-voice-trigger__label").text()).toBe("Custom Trigger Text");
92
92
  });
93
+ it("should respect teleport prop", () => {
94
+ const wrapper = mount(AiVoiceTrigger, {
95
+ props: { variant: "sphere", teleport: true }
96
+ });
97
+ expect(wrapper.props("teleport")).toBe(true);
98
+ const wrapperNoTeleport = mount(AiVoiceTrigger, {
99
+ props: { variant: "sphere", teleport: false }
100
+ });
101
+ expect(wrapperNoTeleport.props("teleport")).toBe(false);
102
+ });
93
103
  });
@@ -19,6 +19,38 @@ const aiVoiceTriggerProps = exports.aiVoiceTriggerProps = {
19
19
  type: Array,
20
20
  default: () => Array(20).fill(5)
21
21
  },
22
+ /**
23
+ * 展示模式
24
+ * - inline: 行内按钮
25
+ * - floating: 悬浮按钮
26
+ * - sphere: 拟物音量球
27
+ */
28
+ variant: {
29
+ type: String,
30
+ default: "inline"
31
+ },
32
+ /**
33
+ * 悬浮位置(仅在 floating/sphere 模式有效)
34
+ */
35
+ position: {
36
+ type: String,
37
+ default: "bottom-right"
38
+ },
39
+ /**
40
+ * 偏移量
41
+ */
42
+ offset: {
43
+ type: Array,
44
+ default: () => [24, 24]
45
+ },
46
+ /**
47
+ * 是否挂载到 body (Teleport)
48
+ * 仅在 floating/sphere 模式有效
49
+ */
50
+ teleport: {
51
+ type: Boolean,
52
+ default: true
53
+ },
22
54
  /**
23
55
  * 主题覆盖变量
24
56
  */
@@ -470,6 +470,44 @@ html.dark {
470
470
  opacity: 1;
471
471
  }
472
472
  }
473
+ @keyframes yh-sphere-pulse {
474
+ 0% {
475
+ transform: scale(1);
476
+ opacity: 0.5;
477
+ }
478
+ 100% {
479
+ transform: scale(3);
480
+ opacity: 0;
481
+ }
482
+ }
483
+ @keyframes yh-sphere-glow {
484
+ 0% {
485
+ transform: scale(1);
486
+ opacity: 0.4;
487
+ }
488
+ 100% {
489
+ transform: scale(1.5);
490
+ opacity: 0.8;
491
+ }
492
+ }
493
+ @keyframes yh-sphere-float {
494
+ 0%, 100% {
495
+ transform: translateY(0);
496
+ }
497
+ 50% {
498
+ transform: translateY(-10px);
499
+ }
500
+ }
501
+ @keyframes yh-sphere-inner-pulse {
502
+ 0% {
503
+ opacity: 0.7;
504
+ transform: scale(0.8);
505
+ }
506
+ 100% {
507
+ opacity: 1;
508
+ transform: scale(1.1);
509
+ }
510
+ }
473
511
  .yh-voice-expand-enter-active,
474
512
  .yh-voice-expand-leave-active {
475
513
  transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
@@ -493,6 +531,65 @@ html.dark {
493
531
  position: relative;
494
532
  font-family: var(--yh-font-family);
495
533
  }
534
+ .yh-ai-voice-trigger--floating {
535
+ z-index: 2000;
536
+ }
537
+ .yh-ai-voice-trigger--floating .yh-ai-voice-trigger__body {
538
+ box-shadow: var(--yh-box-shadow-dark);
539
+ border: 1px solid var(--yh-border-color-lighter);
540
+ }
541
+
542
+ .yh-ai-voice-trigger--sphere {
543
+ --yh-ai-voice-trigger-btn-size: 56px;
544
+ z-index: 2000;
545
+ }
546
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__body {
547
+ padding: 0;
548
+ background: transparent !important;
549
+ box-shadow: none !important;
550
+ overflow: visible;
551
+ }
552
+
553
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__trigger {
554
+ background: var(--yh-color-primary) !important;
555
+ color: #fff !important;
556
+ box-shadow: 0 4px 12px rgba(var(--yh-color-primary-rgb), 0.4);
557
+ z-index: 2;
558
+ }
559
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__trigger.is-active {
560
+ animation: yh-sphere-float 3s ease-in-out infinite;
561
+ }
562
+
563
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-glow {
564
+ position: absolute;
565
+ width: 100%;
566
+ height: 100%;
567
+ background: radial-gradient(circle, var(--yh-color-primary-light-3) 0%, transparent 70%);
568
+ filter: blur(10px);
569
+ opacity: 0.6;
570
+ animation: yh-sphere-glow 2s infinite alternate;
571
+ }
572
+
573
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-pulse {
574
+ position: absolute;
575
+ top: 0;
576
+ left: 0;
577
+ width: 100%;
578
+ height: 100%;
579
+ border-radius: 50%;
580
+ border: 2px solid var(--yh-color-primary-light-5);
581
+ opacity: 0;
582
+ animation: yh-sphere-pulse 2s infinite;
583
+ }
584
+
585
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-inner {
586
+ width: 20px;
587
+ height: 20px;
588
+ border-radius: 50%;
589
+ background: #fff;
590
+ animation: yh-sphere-inner-pulse 1s infinite alternate;
591
+ }
592
+
496
593
  .yh-ai-voice-trigger__body {
497
594
  display: flex;
498
595
  align-items: center;
@@ -508,9 +605,6 @@ html.dark {
508
605
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
509
606
  }
510
607
 
511
- .yh-ai-voice-trigger {
512
- /* Trigger Button */
513
- }
514
608
  .yh-ai-voice-trigger__trigger {
515
609
  display: inline-flex;
516
610
  align-items: center;
@@ -519,11 +613,13 @@ html.dark {
519
613
  background: transparent;
520
614
  cursor: pointer;
521
615
  border-radius: 50%;
522
- width: var(--yh-ai-voice-trigger-btn-size);
616
+ min-width: var(--yh-ai-voice-trigger-btn-size);
523
617
  height: var(--yh-ai-voice-trigger-btn-size);
524
618
  color: var(--yh-text-color-primary, #303133);
525
619
  transition: all 0.2s;
526
620
  flex-shrink: 0;
621
+ position: relative;
622
+ padding: 0;
527
623
  }
528
624
  .yh-ai-voice-trigger__trigger .yh-icon {
529
625
  font-size: 16px;
@@ -535,22 +631,25 @@ html.dark {
535
631
  }
536
632
  .yh-ai-voice-trigger__trigger.is-active {
537
633
  background-color: var(--yh-ai-voice-trigger-active-color);
538
- color: #fff;
634
+ color: #fff !important;
539
635
  animation: yh-pulse-mic 2s infinite;
540
636
  }
541
637
  .yh-ai-voice-trigger__trigger.is-active .yh-icon {
542
638
  transform: scale(0.9);
543
639
  }
544
640
 
545
- .yh-ai-voice-trigger__label {
546
- margin-left: 6px;
547
- font-size: 14px;
548
- display: none;
641
+ .yh-ai-voice-trigger--inline .yh-ai-voice-trigger__trigger:not(.is-active) {
642
+ width: auto;
643
+ padding: 0 12px;
644
+ border-radius: 16px;
645
+ border: 1px solid var(--yh-border-color-lighter);
646
+ background-color: var(--yh-bg-color);
549
647
  }
550
-
551
- .yh-ai-voice-trigger {
552
- /* Waveform Visualizer Area */
648
+ .yh-ai-voice-trigger--inline .yh-ai-voice-trigger__trigger:not(.is-active):hover {
649
+ border-color: var(--yh-color-primary);
650
+ background-color: var(--yh-color-primary-light-9);
553
651
  }
652
+
554
653
  .yh-ai-voice-trigger__visualizer {
555
654
  display: flex;
556
655
  align-items: center;
@@ -14,6 +14,38 @@ export declare const aiVoiceTriggerProps: {
14
14
  readonly type: PropType<number[]>;
15
15
  readonly default: () => any[];
16
16
  };
17
+ /**
18
+ * 展示模式
19
+ * - inline: 行内按钮
20
+ * - floating: 悬浮按钮
21
+ * - sphere: 拟物音量球
22
+ */
23
+ readonly variant: {
24
+ readonly type: PropType<"inline" | "floating" | "sphere">;
25
+ readonly default: "inline";
26
+ };
27
+ /**
28
+ * 悬浮位置(仅在 floating/sphere 模式有效)
29
+ */
30
+ readonly position: {
31
+ readonly type: PropType<"top-left" | "top-right" | "bottom-left" | "bottom-right">;
32
+ readonly default: "bottom-right";
33
+ };
34
+ /**
35
+ * 偏移量
36
+ */
37
+ readonly offset: {
38
+ readonly type: PropType<[number, number]>;
39
+ readonly default: () => number[];
40
+ };
41
+ /**
42
+ * 是否挂载到 body (Teleport)
43
+ * 仅在 floating/sphere 模式有效
44
+ */
45
+ readonly teleport: {
46
+ readonly type: BooleanConstructor;
47
+ readonly default: true;
48
+ };
17
49
  /**
18
50
  * 主题覆盖变量
19
51
  */
@@ -1,6 +1,6 @@
1
- declare var __VLS_13: {};
1
+ declare var __VLS_17: {};
2
2
  type __VLS_Slots = {} & {
3
- default?: (props: typeof __VLS_13) => any;
3
+ default?: (props: typeof __VLS_17) => any;
4
4
  };
5
5
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
6
6
  readonly recording: {
@@ -11,12 +11,32 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
11
11
  readonly type: import("vue").PropType<number[]>;
12
12
  readonly default: () => any[];
13
13
  };
14
+ readonly variant: {
15
+ readonly type: import("vue").PropType<"inline" | "floating" | "sphere">;
16
+ readonly default: "inline";
17
+ };
18
+ readonly position: {
19
+ readonly type: import("vue").PropType<"top-left" | "top-right" | "bottom-left" | "bottom-right">;
20
+ readonly default: "bottom-right";
21
+ };
22
+ readonly offset: {
23
+ readonly type: import("vue").PropType<[number, number]>;
24
+ readonly default: () => number[];
25
+ };
26
+ readonly teleport: {
27
+ readonly type: BooleanConstructor;
28
+ readonly default: true;
29
+ };
14
30
  readonly themeOverrides: {
15
31
  readonly type: import("vue").PropType<import("@yh-ui/theme").ComponentThemeVars>;
16
32
  readonly default: undefined;
17
33
  };
18
34
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any, string, import("vue").PublicProps, any, {
35
+ readonly offset: [number, number];
36
+ readonly position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
19
37
  readonly themeOverrides: import("@yh-ui/theme").ComponentThemeVars;
38
+ readonly variant: "inline" | "floating" | "sphere";
39
+ readonly teleport: boolean;
20
40
  readonly recording: boolean;
21
41
  readonly amplitudes: number[];
22
42
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -13,6 +13,38 @@ export const aiVoiceTriggerProps = {
13
13
  type: Array,
14
14
  default: () => Array(20).fill(5)
15
15
  },
16
+ /**
17
+ * 展示模式
18
+ * - inline: 行内按钮
19
+ * - floating: 悬浮按钮
20
+ * - sphere: 拟物音量球
21
+ */
22
+ variant: {
23
+ type: String,
24
+ default: "inline"
25
+ },
26
+ /**
27
+ * 悬浮位置(仅在 floating/sphere 模式有效)
28
+ */
29
+ position: {
30
+ type: String,
31
+ default: "bottom-right"
32
+ },
33
+ /**
34
+ * 偏移量
35
+ */
36
+ offset: {
37
+ type: Array,
38
+ default: () => [24, 24]
39
+ },
40
+ /**
41
+ * 是否挂载到 body (Teleport)
42
+ * 仅在 floating/sphere 模式有效
43
+ */
44
+ teleport: {
45
+ type: Boolean,
46
+ default: true
47
+ },
16
48
  /**
17
49
  * 主题覆盖变量
18
50
  */
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { useNamespace, useLocale } from "@yh-ui/hooks";
3
3
  import { useComponentTheme } from "@yh-ui/theme";
4
- import { computed, ref, onBeforeUnmount } from "vue";
4
+ import { computed, ref, onBeforeUnmount, watch } from "vue";
5
5
  import { aiVoiceTriggerProps, aiVoiceTriggerEmits } from "./ai-voice-trigger";
6
6
  import { YhIcon } from "../../icon";
7
7
  defineOptions({
@@ -17,12 +17,25 @@ const { themeStyle } = useComponentTheme(
17
17
  );
18
18
  const localRecording = ref(props.recording);
19
19
  let visualizerTimer = null;
20
- const simulatedAmplitudes = ref([]);
20
+ const simulatedAmplitudes = ref(Array(20).fill(5));
21
+ watch(
22
+ () => props.amplitudes,
23
+ (newAmps) => {
24
+ if (newAmps && newAmps.length > 0) {
25
+ const hasData = newAmps.some((a) => a > 6 || a > 0 && a !== 5);
26
+ if (hasData) {
27
+ simulatedAmplitudes.value = [...newAmps];
28
+ }
29
+ }
30
+ },
31
+ { immediate: true, deep: true }
32
+ );
21
33
  const syncAmplitudes = () => {
22
- if (props.amplitudes && props.amplitudes.length > 0 && props.amplitudes.some((a) => a !== 5)) {
23
- simulatedAmplitudes.value = [...props.amplitudes];
24
- } else {
25
- simulatedAmplitudes.value = Array.from({ length: 20 }, () => 10 + Math.random() * 40);
34
+ if (props.recording || localRecording.value) {
35
+ const hasData = props.amplitudes && props.amplitudes.some((a) => a > 6 || a > 0 && a !== 5);
36
+ if (!hasData) {
37
+ simulatedAmplitudes.value = Array.from({ length: 20 }, () => 10 + Math.random() * 40);
38
+ }
26
39
  }
27
40
  };
28
41
  const toggleRecording = () => {
@@ -62,43 +75,82 @@ onBeforeUnmount(() => {
62
75
  </script>
63
76
 
64
77
  <template>
65
- <div :class="[ns.b(), ns.is('recording', props.recording || localRecording)]" :style="themeStyle">
66
- <div :class="ns.e('body')">
67
- <!-- Waveform Visualizer -->
68
- <Transition name="yh-voice-expand">
69
- <div v-if="props.recording || localRecording" :class="ns.e('visualizer')">
70
- <div :class="ns.e('bars')">
71
- <span
72
- v-for="(amp, i) in simulatedAmplitudes"
73
- :key="i"
74
- :style="{ height: amp + 'px' }"
75
- :class="ns.e('bar')"
76
- ></span>
78
+ <Teleport to="body" :disabled="props.variant === 'inline' || !props.teleport">
79
+ <div
80
+ :class="[ns.b(), ns.m(props.variant), ns.m(props.position), ns.is('recording', props.recording || localRecording)]"
81
+ :style="[props.variant !== 'inline' ? {
82
+ position: props.teleport ? 'fixed' : 'relative',
83
+ [props.position.split('-')[0]]: props.teleport ? props.offset[0] + 'px' : 'auto',
84
+ [props.position.split('-')[1]]: props.teleport ? props.offset[1] + 'px' : 'auto'
85
+ } : {}, themeStyle]"
86
+ >
87
+ <div :class="ns.e('body')">
88
+ <!-- Waveform Visualizer -->
89
+ <Transition name="yh-voice-expand">
90
+ <div v-if="props.recording || localRecording" :class="ns.e('visualizer')">
91
+ <!-- Sphere Visualizer (Pulsing Ball) -->
92
+ <template v-if="props.variant === 'sphere'">
93
+ <div :class="ns.e('sphere-glow')"></div>
94
+ <div
95
+ v-for="i in 3"
96
+ :key="i"
97
+ :class="ns.e('sphere-pulse')"
98
+ :style="{
99
+ animationDelay: i * 0.5 + 's',
100
+ transform: `scale(${1 + (simulatedAmplitudes[i] || 0) / 100})`
101
+ }"
102
+ ></div>
103
+ </template>
104
+
105
+ <!-- Standard Wave Visualizer -->
106
+ <div v-else :class="ns.e('bars')">
107
+ <span
108
+ v-for="(amp, i) in simulatedAmplitudes"
109
+ :key="i"
110
+ :style="{
111
+ height: amp + 'px'
112
+ }"
113
+ :class="ns.e('bar')"
114
+ ></span>
115
+ </div>
116
+
117
+ <span v-if="props.variant !== 'sphere'" :class="ns.e('hint')">{{
118
+ t("ai.voice.listening") || "Listening..."
119
+ }}</span>
120
+
121
+ <!-- Cancel Button -->
122
+ <button :class="ns.e('cancel')" @click="handleCancel" title="Cancel">
123
+ <YhIcon name="close" />
124
+ </button>
77
125
  </div>
78
- <span :class="ns.e('hint')">{{ t("ai.voice.listening") || "Listening..." }}</span>
79
- <!-- Cancel Button -->
80
- <button :class="ns.e('cancel')" @click="handleCancel" title="Cancel">
81
- <YhIcon name="close" />
82
- </button>
83
- </div>
84
- </Transition>
126
+ </Transition>
85
127
 
86
- <!-- Main Trigger Button -->
87
- <button
88
- :class="[ns.e('trigger'), {
128
+ <!-- Main Trigger Button -->
129
+ <button
130
+ :class="[ns.e('trigger'), {
89
131
  'is-active': props.recording || localRecording
90
132
  }]"
91
- @click="toggleRecording"
92
- >
93
- <span :class="ns.e('mic')">
94
- <YhIcon :name="props.recording || localRecording ? 'video-pause' : 'microphone'" />
95
- </span>
96
- <span v-if="!(props.recording || localRecording)" :class="ns.e('label')">
97
- <slot>{{ t("ai.voice.trigger") || "" }}</slot>
98
- </span>
99
- </button>
133
+ @click="toggleRecording"
134
+ >
135
+ <span :class="ns.e('mic')">
136
+ <template v-if="props.variant === 'sphere' && (props.recording || localRecording)">
137
+ <div :class="ns.e('sphere-inner')"></div>
138
+ </template>
139
+ <YhIcon
140
+ v-else
141
+ :name="props.recording || localRecording ? 'video-pause' : 'microphone'"
142
+ />
143
+ </span>
144
+ <span
145
+ v-if="!(props.recording || localRecording) && props.variant === 'inline'"
146
+ :class="ns.e('label')"
147
+ >
148
+ <slot>{{ t("ai.voice.trigger") || "" }}</slot>
149
+ </span>
150
+ </button>
151
+ </div>
100
152
  </div>
101
- </div>
153
+ </Teleport>
102
154
  </template>
103
155
 
104
156
  <style>
@@ -574,6 +626,44 @@ html.dark {
574
626
  opacity: 1;
575
627
  }
576
628
  }
629
+ @keyframes yh-sphere-pulse {
630
+ 0% {
631
+ transform: scale(1);
632
+ opacity: 0.5;
633
+ }
634
+ 100% {
635
+ transform: scale(3);
636
+ opacity: 0;
637
+ }
638
+ }
639
+ @keyframes yh-sphere-glow {
640
+ 0% {
641
+ transform: scale(1);
642
+ opacity: 0.4;
643
+ }
644
+ 100% {
645
+ transform: scale(1.5);
646
+ opacity: 0.8;
647
+ }
648
+ }
649
+ @keyframes yh-sphere-float {
650
+ 0%, 100% {
651
+ transform: translateY(0);
652
+ }
653
+ 50% {
654
+ transform: translateY(-10px);
655
+ }
656
+ }
657
+ @keyframes yh-sphere-inner-pulse {
658
+ 0% {
659
+ opacity: 0.7;
660
+ transform: scale(0.8);
661
+ }
662
+ 100% {
663
+ opacity: 1;
664
+ transform: scale(1.1);
665
+ }
666
+ }
577
667
  .yh-voice-expand-enter-active,
578
668
  .yh-voice-expand-leave-active {
579
669
  transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
@@ -597,6 +687,65 @@ html.dark {
597
687
  position: relative;
598
688
  font-family: var(--yh-font-family);
599
689
  }
690
+ .yh-ai-voice-trigger--floating {
691
+ z-index: 2000;
692
+ }
693
+ .yh-ai-voice-trigger--floating .yh-ai-voice-trigger__body {
694
+ box-shadow: var(--yh-box-shadow-dark);
695
+ border: 1px solid var(--yh-border-color-lighter);
696
+ }
697
+
698
+ .yh-ai-voice-trigger--sphere {
699
+ --yh-ai-voice-trigger-btn-size: 56px;
700
+ z-index: 2000;
701
+ }
702
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__body {
703
+ padding: 0;
704
+ background: transparent !important;
705
+ box-shadow: none !important;
706
+ overflow: visible;
707
+ }
708
+
709
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__trigger {
710
+ background: var(--yh-color-primary) !important;
711
+ color: #fff !important;
712
+ box-shadow: 0 4px 12px rgba(var(--yh-color-primary-rgb), 0.4);
713
+ z-index: 2;
714
+ }
715
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__trigger.is-active {
716
+ animation: yh-sphere-float 3s ease-in-out infinite;
717
+ }
718
+
719
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-glow {
720
+ position: absolute;
721
+ width: 100%;
722
+ height: 100%;
723
+ background: radial-gradient(circle, var(--yh-color-primary-light-3) 0%, transparent 70%);
724
+ filter: blur(10px);
725
+ opacity: 0.6;
726
+ animation: yh-sphere-glow 2s infinite alternate;
727
+ }
728
+
729
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-pulse {
730
+ position: absolute;
731
+ top: 0;
732
+ left: 0;
733
+ width: 100%;
734
+ height: 100%;
735
+ border-radius: 50%;
736
+ border: 2px solid var(--yh-color-primary-light-5);
737
+ opacity: 0;
738
+ animation: yh-sphere-pulse 2s infinite;
739
+ }
740
+
741
+ .yh-ai-voice-trigger--sphere .yh-ai-voice-trigger__sphere-inner {
742
+ width: 20px;
743
+ height: 20px;
744
+ border-radius: 50%;
745
+ background: #fff;
746
+ animation: yh-sphere-inner-pulse 1s infinite alternate;
747
+ }
748
+
600
749
  .yh-ai-voice-trigger__body {
601
750
  display: flex;
602
751
  align-items: center;
@@ -612,9 +761,6 @@ html.dark {
612
761
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
613
762
  }
614
763
 
615
- .yh-ai-voice-trigger {
616
- /* Trigger Button */
617
- }
618
764
  .yh-ai-voice-trigger__trigger {
619
765
  display: inline-flex;
620
766
  align-items: center;
@@ -623,11 +769,13 @@ html.dark {
623
769
  background: transparent;
624
770
  cursor: pointer;
625
771
  border-radius: 50%;
626
- width: var(--yh-ai-voice-trigger-btn-size);
772
+ min-width: var(--yh-ai-voice-trigger-btn-size);
627
773
  height: var(--yh-ai-voice-trigger-btn-size);
628
774
  color: var(--yh-text-color-primary, #303133);
629
775
  transition: all 0.2s;
630
776
  flex-shrink: 0;
777
+ position: relative;
778
+ padding: 0;
631
779
  }
632
780
  .yh-ai-voice-trigger__trigger .yh-icon {
633
781
  font-size: 16px;
@@ -639,22 +787,25 @@ html.dark {
639
787
  }
640
788
  .yh-ai-voice-trigger__trigger.is-active {
641
789
  background-color: var(--yh-ai-voice-trigger-active-color);
642
- color: #fff;
790
+ color: #fff !important;
643
791
  animation: yh-pulse-mic 2s infinite;
644
792
  }
645
793
  .yh-ai-voice-trigger__trigger.is-active .yh-icon {
646
794
  transform: scale(0.9);
647
795
  }
648
796
 
649
- .yh-ai-voice-trigger__label {
650
- margin-left: 6px;
651
- font-size: 14px;
652
- display: none;
797
+ .yh-ai-voice-trigger--inline .yh-ai-voice-trigger__trigger:not(.is-active) {
798
+ width: auto;
799
+ padding: 0 12px;
800
+ border-radius: 16px;
801
+ border: 1px solid var(--yh-border-color-lighter);
802
+ background-color: var(--yh-bg-color);
653
803
  }
654
-
655
- .yh-ai-voice-trigger {
656
- /* Waveform Visualizer Area */
804
+ .yh-ai-voice-trigger--inline .yh-ai-voice-trigger__trigger:not(.is-active):hover {
805
+ border-color: var(--yh-color-primary);
806
+ background-color: var(--yh-color-primary-light-9);
657
807
  }
808
+
658
809
  .yh-ai-voice-trigger__visualizer {
659
810
  display: flex;
660
811
  align-items: center;