@yh-ui/components 0.1.12 → 0.1.16

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
@@ -0,0 +1,68 @@
1
+ import { type AiMentionOption } from './ai-mention';
2
+ declare var __VLS_22: any, __VLS_23: any;
3
+ type __VLS_Slots = {} & {
4
+ [K in NonNullable<typeof __VLS_22>]?: (props: typeof __VLS_23) => any;
5
+ };
6
+ declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ readonly modelValue: {
8
+ readonly type: StringConstructor;
9
+ readonly default: "";
10
+ };
11
+ readonly types: {
12
+ readonly type: import("vue").PropType<import("./ai-mention").AiMentionType[]>;
13
+ readonly default: () => string[];
14
+ };
15
+ readonly options: {
16
+ readonly type: import("vue").PropType<AiMentionOption[]>;
17
+ readonly default: () => never[];
18
+ };
19
+ readonly triggers: {
20
+ readonly type: import("vue").PropType<string[]>;
21
+ readonly default: () => string[];
22
+ };
23
+ readonly type: {
24
+ readonly type: import("vue").PropType<"input" | "textarea">;
25
+ readonly default: "textarea";
26
+ };
27
+ readonly placeholder: StringConstructor;
28
+ readonly disabled: BooleanConstructor;
29
+ readonly size: import("vue").PropType<"large" | "default" | "small">;
30
+ readonly maxLength: NumberConstructor;
31
+ readonly rows: {
32
+ readonly type: NumberConstructor;
33
+ readonly default: 3;
34
+ };
35
+ readonly loading: BooleanConstructor;
36
+ readonly themeOverrides: {
37
+ readonly type: import("vue").PropType<import("@yh-ui/theme").ComponentThemeVars>;
38
+ readonly default: () => {};
39
+ };
40
+ readonly filterOption: {
41
+ readonly type: import("vue").PropType<((keyword: string, option: import("../../mention").MentionOption) => boolean) | false>;
42
+ readonly default: undefined;
43
+ };
44
+ }>, {
45
+ focus: () => void | undefined;
46
+ blur: () => void | undefined;
47
+ clear: () => void | undefined;
48
+ getRef: () => HTMLTextAreaElement | HTMLInputElement | undefined;
49
+ insertMention: (option: AiMentionOption, trigger?: string) => void | undefined;
50
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any, string, import("vue").PublicProps, any, {
51
+ readonly disabled: boolean;
52
+ readonly themeOverrides: import("@yh-ui/theme").ComponentThemeVars;
53
+ readonly type: "textarea" | "input";
54
+ readonly loading: boolean;
55
+ readonly triggers: string[];
56
+ readonly options: AiMentionOption[];
57
+ readonly modelValue: string;
58
+ readonly filterOption: false | ((keyword: string, option: import("../../mention").MentionOption) => boolean);
59
+ readonly rows: number;
60
+ readonly types: ("table" | "document" | "agent" | "knowledge")[];
61
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
62
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
63
+ export default _default;
64
+ type __VLS_WithSlots<T, S> = T & {
65
+ new (): {
66
+ $slots: S;
67
+ };
68
+ };
@@ -6,6 +6,12 @@ var _vue = require("vue");
6
6
  var _aiSender = _interopRequireDefault(require("../src/ai-sender.vue"));
7
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
8
  (0, _vitest.describe)("YhAiSender", () => {
9
+ beforeEach(() => {
10
+ _vitest.vi.useFakeTimers();
11
+ });
12
+ afterEach(() => {
13
+ _vitest.vi.useRealTimers();
14
+ });
9
15
  (0, _vitest.it)("should render with base class", () => {
10
16
  const wrapper = (0, _testUtils.mount)(_aiSender.default);
11
17
  (0, _vitest.expect)(wrapper.find(".yh-ai-sender").exists()).toBe(true);
@@ -52,7 +58,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
52
58
  disabled: true
53
59
  }
54
60
  });
55
- (0, _vitest.expect)(wrapper.classes()).toContain("is-disabled");
61
+ (0, _vitest.expect)(wrapper.find(".yh-ai-sender").classes()).toContain("is-disabled");
56
62
  });
57
63
  (0, _vitest.it)("should disable textarea when disabled=true", () => {
58
64
  const wrapper = (0, _testUtils.mount)(_aiSender.default, {
@@ -68,7 +74,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
68
74
  loading: true
69
75
  }
70
76
  });
71
- (0, _vitest.expect)(wrapper.classes()).toContain("is-loading");
77
+ (0, _vitest.expect)(wrapper.find(".yh-ai-sender").classes()).toContain("is-loading");
72
78
  });
73
79
  (0, _vitest.it)("should disable textarea when loading=true", () => {
74
80
  const wrapper = (0, _testUtils.mount)(_aiSender.default, {
@@ -194,13 +200,15 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
194
200
  (0, _vitest.it)("should apply is-focused on textarea focus", async () => {
195
201
  const wrapper = (0, _testUtils.mount)(_aiSender.default);
196
202
  await wrapper.find("textarea").trigger("focus");
197
- (0, _vitest.expect)(wrapper.classes()).toContain("is-focused");
203
+ (0, _vitest.expect)(wrapper.find(".yh-ai-sender").classes()).toContain("is-focused");
198
204
  });
199
205
  (0, _vitest.it)("should remove is-focused on textarea blur", async () => {
200
206
  const wrapper = (0, _testUtils.mount)(_aiSender.default);
201
- await wrapper.find("textarea").trigger("focus");
202
- await wrapper.find("textarea").trigger("blur");
203
- (0, _vitest.expect)(wrapper.classes()).not.toContain("is-focused");
207
+ await wrapper.get("textarea").trigger("focus");
208
+ await wrapper.get("textarea").trigger("blur");
209
+ _vitest.vi.advanceTimersByTime(400);
210
+ await (0, _vue.nextTick)();
211
+ (0, _vitest.expect)(wrapper.find(".yh-ai-sender").classes()).not.toContain("is-focused");
204
212
  });
205
213
  (0, _vitest.it)("should render prefix slot", () => {
206
214
  const wrapper = (0, _testUtils.mount)(_aiSender.default, {
@@ -253,10 +261,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
253
261
  commands
254
262
  }
255
263
  });
256
- const textarea = wrapper.find("textarea");
264
+ const textarea = wrapper.get("textarea");
257
265
  await textarea.setValue("/h");
258
266
  textarea.element.selectionStart = 2;
259
267
  await textarea.trigger("input");
268
+ await (0, _vue.nextTick)();
260
269
  (0, _vitest.expect)(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(true);
261
270
  (0, _vitest.expect)(wrapper.findAll(".yh-ai-sender__command-item").length).toBe(1);
262
271
  });
@@ -312,7 +321,6 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
312
321
  (0, _vitest.expect)(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(false);
313
322
  });
314
323
  (0, _vitest.it)("should hide commands on blur after delay", async () => {
315
- _vitest.vi.useFakeTimers();
316
324
  const wrapper = (0, _testUtils.mount)(_aiSender.default, {
317
325
  props: {
318
326
  commands
@@ -323,10 +331,9 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
323
331
  textarea.element.selectionStart = 1;
324
332
  await textarea.trigger("input");
325
333
  await textarea.trigger("blur");
326
- _vitest.vi.advanceTimersByTime(250);
334
+ _vitest.vi.advanceTimersByTime(400);
327
335
  await (0, _vue.nextTick)();
328
336
  (0, _vitest.expect)(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(false);
329
- _vitest.vi.useRealTimers();
330
337
  });
331
338
  });
332
339
  (0, _vitest.describe)("Attachments", () => {
@@ -3,6 +3,12 @@ import { mount } from "@vue/test-utils";
3
3
  import { nextTick } from "vue";
4
4
  import AiSender from "../src/ai-sender.vue";
5
5
  describe("YhAiSender", () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ });
9
+ afterEach(() => {
10
+ vi.useRealTimers();
11
+ });
6
12
  it("should render with base class", () => {
7
13
  const wrapper = mount(AiSender);
8
14
  expect(wrapper.find(".yh-ai-sender").exists()).toBe(true);
@@ -33,7 +39,7 @@ describe("YhAiSender", () => {
33
39
  });
34
40
  it("should apply is-disabled class when disabled=true", () => {
35
41
  const wrapper = mount(AiSender, { props: { disabled: true } });
36
- expect(wrapper.classes()).toContain("is-disabled");
42
+ expect(wrapper.find(".yh-ai-sender").classes()).toContain("is-disabled");
37
43
  });
38
44
  it("should disable textarea when disabled=true", () => {
39
45
  const wrapper = mount(AiSender, { props: { disabled: true } });
@@ -41,7 +47,7 @@ describe("YhAiSender", () => {
41
47
  });
42
48
  it("should apply is-loading class when loading=true", () => {
43
49
  const wrapper = mount(AiSender, { props: { loading: true } });
44
- expect(wrapper.classes()).toContain("is-loading");
50
+ expect(wrapper.find(".yh-ai-sender").classes()).toContain("is-loading");
45
51
  });
46
52
  it("should disable textarea when loading=true", () => {
47
53
  const wrapper = mount(AiSender, { props: { loading: true } });
@@ -114,13 +120,15 @@ describe("YhAiSender", () => {
114
120
  it("should apply is-focused on textarea focus", async () => {
115
121
  const wrapper = mount(AiSender);
116
122
  await wrapper.find("textarea").trigger("focus");
117
- expect(wrapper.classes()).toContain("is-focused");
123
+ expect(wrapper.find(".yh-ai-sender").classes()).toContain("is-focused");
118
124
  });
119
125
  it("should remove is-focused on textarea blur", async () => {
120
126
  const wrapper = mount(AiSender);
121
- await wrapper.find("textarea").trigger("focus");
122
- await wrapper.find("textarea").trigger("blur");
123
- expect(wrapper.classes()).not.toContain("is-focused");
127
+ await wrapper.get("textarea").trigger("focus");
128
+ await wrapper.get("textarea").trigger("blur");
129
+ vi.advanceTimersByTime(400);
130
+ await nextTick();
131
+ expect(wrapper.find(".yh-ai-sender").classes()).not.toContain("is-focused");
124
132
  });
125
133
  it("should render prefix slot", () => {
126
134
  const wrapper = mount(AiSender, {
@@ -153,10 +161,11 @@ describe("YhAiSender", () => {
153
161
  ];
154
162
  it("should show slash commands panel when triggering /", async () => {
155
163
  const wrapper = mount(AiSender, { props: { commands } });
156
- const textarea = wrapper.find("textarea");
164
+ const textarea = wrapper.get("textarea");
157
165
  await textarea.setValue("/h");
158
166
  textarea.element.selectionStart = 2;
159
167
  await textarea.trigger("input");
168
+ await nextTick();
160
169
  expect(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(true);
161
170
  expect(wrapper.findAll(".yh-ai-sender__command-item").length).toBe(1);
162
171
  });
@@ -192,17 +201,15 @@ describe("YhAiSender", () => {
192
201
  expect(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(false);
193
202
  });
194
203
  it("should hide commands on blur after delay", async () => {
195
- vi.useFakeTimers();
196
204
  const wrapper = mount(AiSender, { props: { commands } });
197
205
  const textarea = wrapper.find("textarea");
198
206
  await textarea.setValue("/");
199
207
  textarea.element.selectionStart = 1;
200
208
  await textarea.trigger("input");
201
209
  await textarea.trigger("blur");
202
- vi.advanceTimersByTime(250);
210
+ vi.advanceTimersByTime(400);
203
211
  await nextTick();
204
212
  expect(wrapper.find(".yh-ai-sender__command-panel").exists()).toBe(false);
205
- vi.useRealTimers();
206
213
  });
207
214
  });
208
215
  describe("Attachments", () => {
@@ -58,6 +58,13 @@ const aiSenderProps = exports.aiSenderProps = {
58
58
  type: Array,
59
59
  default: () => []
60
60
  },
61
+ /**
62
+ * AI 提及配置
63
+ */
64
+ mentionOptions: {
65
+ type: Array,
66
+ default: () => []
67
+ },
61
68
  /**
62
69
  * 已选附件列表
63
70
  */
@@ -105,6 +105,13 @@ export declare const aiSenderProps: {
105
105
  readonly type: PropType<AiCommand[]>;
106
106
  readonly default: () => never[];
107
107
  };
108
+ /**
109
+ * AI 提及配置
110
+ */
111
+ readonly mentionOptions: {
112
+ readonly type: PropType<import("../../ai-mention").AiMentionOption[]>;
113
+ readonly default: () => never[];
114
+ };
108
115
  /**
109
116
  * 已选附件列表
110
117
  */
@@ -1,5 +1,5 @@
1
1
  import { type AiCommand } from './ai-sender';
2
- declare var __VLS_9: {}, __VLS_23: {}, __VLS_37: {
2
+ declare var __VLS_9: {}, __VLS_36: {}, __VLS_50: {
3
3
  disabled: any;
4
4
  loading: any;
5
5
  submit: any;
@@ -7,9 +7,9 @@ declare var __VLS_9: {}, __VLS_23: {}, __VLS_37: {
7
7
  type __VLS_Slots = {} & {
8
8
  prefix?: (props: typeof __VLS_9) => any;
9
9
  } & {
10
- actions?: (props: typeof __VLS_23) => any;
10
+ actions?: (props: typeof __VLS_36) => any;
11
11
  } & {
12
- submit?: (props: typeof __VLS_37) => any;
12
+ submit?: (props: typeof __VLS_50) => any;
13
13
  };
14
14
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
15
15
  readonly modelValue: {
@@ -41,6 +41,10 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
41
41
  readonly type: import("vue").PropType<AiCommand[]>;
42
42
  readonly default: () => never[];
43
43
  };
44
+ readonly mentionOptions: {
45
+ readonly type: import("vue").PropType<import("../../ai-mention").AiMentionOption[]>;
46
+ readonly default: () => never[];
47
+ };
44
48
  readonly attachments: {
45
49
  readonly type: import("vue").PropType<import("./ai-sender").AiAttachment[]>;
46
50
  readonly default: () => never[];
@@ -58,6 +62,7 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
58
62
  readonly clearable: boolean;
59
63
  readonly showWordLimit: boolean;
60
64
  readonly commands: AiCommand[];
65
+ readonly mentionOptions: import("../../ai-mention").AiMentionOption[];
61
66
  readonly attachments: import("./ai-sender").AiAttachment[];
62
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
63
68
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -52,6 +52,13 @@ export const aiSenderProps = {
52
52
  type: Array,
53
53
  default: () => []
54
54
  },
55
+ /**
56
+ * AI 提及配置
57
+ */
58
+ mentionOptions: {
59
+ type: Array,
60
+ default: () => []
61
+ },
55
62
  /**
56
63
  * 已选附件列表
57
64
  */
@@ -5,6 +5,7 @@ import { aiSenderProps, aiSenderEmits } from "./ai-sender";
5
5
  import { YhButton } from "../../button";
6
6
  import { YhIcon } from "../../icon";
7
7
  import { YhImage } from "../../image";
8
+ import { YhAiMention } from "../../ai-mention";
8
9
  import { useComponentTheme } from "@yh-ui/theme";
9
10
  defineOptions({
10
11
  name: "YhAiSender"
@@ -17,8 +18,8 @@ const { themeStyle } = useComponentTheme(
17
18
  "ai-sender",
18
19
  computed(() => props.themeOverrides)
19
20
  );
20
- const textareaRef = ref();
21
- const localValue = ref(props.modelValue);
21
+ const textareaRef = ref(null);
22
+ const localValue = ref(props.modelValue ?? "");
22
23
  const isFocused = ref(false);
23
24
  const showCommands = ref(false);
24
25
  const commandSearch = ref("");
@@ -46,15 +47,16 @@ const filteredCommands = computed(() => {
46
47
  );
47
48
  });
48
49
  const autoResize = () => {
49
- const el = textareaRef.value;
50
+ const el = textareaRef.value?.getRef();
50
51
  if (!el) return;
51
52
  el.style.height = "auto";
52
53
  el.style.height = `${el.scrollHeight}px`;
53
54
  };
54
- const handleInput = (e) => {
55
- const val = e.target.value;
55
+ const handleInput = (val) => {
56
56
  innerValue.value = val;
57
- const cursorPosition = e.target.selectionStart;
57
+ const el = textareaRef.value?.getRef();
58
+ if (!el) return;
59
+ const cursorPosition = el.selectionStart || 0;
58
60
  const textBeforeCursor = val.slice(0, cursorPosition);
59
61
  const lastSlashIndex = textBeforeCursor.lastIndexOf("/");
60
62
  if (lastSlashIndex !== -1) {
@@ -77,7 +79,8 @@ const handleInput = (e) => {
77
79
  };
78
80
  const handleSelectCommand = (command) => {
79
81
  const val = innerValue.value;
80
- const cursorPosition = textareaRef.value?.selectionStart || 0;
82
+ const el = textareaRef.value?.getRef();
83
+ const cursorPosition = el?.selectionStart || 0;
81
84
  const textBeforeCursor = val.slice(0, cursorPosition);
82
85
  const lastSlashIndex = textBeforeCursor.lastIndexOf("/");
83
86
  const textAfterCursor = val.slice(cursorPosition);
@@ -107,7 +110,7 @@ const handleKeyDown = (e) => {
107
110
  return;
108
111
  }
109
112
  if (e.key === "Enter" && !e.shiftKey) {
110
- if (!innerValue.value.trim() || props.loading || props.disabled) {
113
+ if (!innerValue.value?.trim() || props.loading || props.disabled) {
111
114
  e.preventDefault();
112
115
  } else {
113
116
  e.preventDefault();
@@ -116,12 +119,13 @@ const handleKeyDown = (e) => {
116
119
  }
117
120
  };
118
121
  const handleSend = () => {
119
- if (!innerValue.value.trim() || props.loading || props.disabled) return;
122
+ if (!innerValue.value?.trim() || props.loading || props.disabled) return;
120
123
  emit("send", innerValue.value);
121
124
  innerValue.value = "";
122
125
  nextTick(() => {
123
- if (textareaRef.value) {
124
- textareaRef.value.style.height = "auto";
126
+ const el = textareaRef.value?.getRef();
127
+ if (el) {
128
+ el.style.height = "auto";
125
129
  }
126
130
  });
127
131
  };
@@ -214,19 +218,22 @@ const handleFocus = (e) => {
214
218
  </div>
215
219
 
216
220
  <div :class="ns.e('textarea-container')">
217
- <textarea
221
+ <YhAiMention
218
222
  ref="textareaRef"
219
223
  v-model="innerValue"
224
+ type="textarea"
220
225
  :class="ns.e('textarea')"
221
226
  :placeholder="placeholder === 'Send a message...' ? t('ai.sender.placeholder') : placeholder"
222
227
  :disabled="disabled || loading"
223
- :maxlength="maxLength"
224
- rows="1"
228
+ :max-length="maxLength"
229
+ :rows="1"
230
+ :options="mentionOptions"
231
+ :trigger="['@', '#']"
225
232
  @focus="handleFocus"
226
233
  @blur="handleBlur"
227
234
  @input="handleInput"
228
235
  @keydown="handleKeyDown"
229
- ></textarea>
236
+ />
230
237
  </div>
231
238
 
232
239
  <div :class="ns.e('suffix')">
@@ -252,14 +259,14 @@ const handleFocus = (e) => {
252
259
 
253
260
  <slot
254
261
  name="submit"
255
- :disabled="!innerValue.trim() || disabled"
262
+ :disabled="!innerValue?.trim() || disabled"
256
263
  :loading="loading"
257
264
  :submit="handleSend"
258
265
  >
259
266
  <YhButton
260
- :type="innerValue.trim() && !disabled && !loading ? 'primary' : 'default'"
267
+ :type="innerValue?.trim() && !disabled && !loading ? 'primary' : 'default'"
261
268
  :class="ns.e('send-btn')"
262
- :disabled="!innerValue.trim() || disabled"
269
+ :disabled="!innerValue?.trim() || disabled"
263
270
  :loading="loading"
264
271
  @click="handleSend"
265
272
  circle
@@ -1,5 +1,5 @@
1
1
  import { type AiCommand } from './ai-sender';
2
- declare var __VLS_9: {}, __VLS_23: {}, __VLS_37: {
2
+ declare var __VLS_9: {}, __VLS_36: {}, __VLS_50: {
3
3
  disabled: any;
4
4
  loading: any;
5
5
  submit: any;
@@ -7,9 +7,9 @@ declare var __VLS_9: {}, __VLS_23: {}, __VLS_37: {
7
7
  type __VLS_Slots = {} & {
8
8
  prefix?: (props: typeof __VLS_9) => any;
9
9
  } & {
10
- actions?: (props: typeof __VLS_23) => any;
10
+ actions?: (props: typeof __VLS_36) => any;
11
11
  } & {
12
- submit?: (props: typeof __VLS_37) => any;
12
+ submit?: (props: typeof __VLS_50) => any;
13
13
  };
14
14
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
15
15
  readonly modelValue: {
@@ -41,6 +41,10 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
41
41
  readonly type: import("vue").PropType<AiCommand[]>;
42
42
  readonly default: () => never[];
43
43
  };
44
+ readonly mentionOptions: {
45
+ readonly type: import("vue").PropType<import("../../ai-mention").AiMentionOption[]>;
46
+ readonly default: () => never[];
47
+ };
44
48
  readonly attachments: {
45
49
  readonly type: import("vue").PropType<import("./ai-sender").AiAttachment[]>;
46
50
  readonly default: () => never[];
@@ -58,6 +62,7 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
58
62
  readonly clearable: boolean;
59
63
  readonly showWordLimit: boolean;
60
64
  readonly commands: AiCommand[];
65
+ readonly mentionOptions: import("../../ai-mention").AiMentionOption[];
61
66
  readonly attachments: import("./ai-sender").AiAttachment[];
62
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
63
68
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -1,7 +1,7 @@
1
1
  import { type AiSourceItem } from './ai-sources';
2
- declare var __VLS_77: {};
2
+ declare var __VLS_65: {};
3
3
  type __VLS_Slots = {} & {
4
- default?: (props: typeof __VLS_77) => any;
4
+ default?: (props: typeof __VLS_65) => any;
5
5
  };
6
6
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
7
  readonly sources: {
@@ -4,6 +4,7 @@ import { ref, computed } from "vue";
4
4
  import { aiSourcesProps, aiSourcesEmits } from "./ai-sources";
5
5
  import { YhIcon } from "../../icon";
6
6
  import { YhTooltip } from "../../tooltip";
7
+ import { YhDrawer } from "../../drawer";
7
8
  import { useComponentTheme } from "@yh-ui/theme";
8
9
  defineOptions({
9
10
  name: "YhAiSources"
@@ -76,69 +77,52 @@ const openDrawer = (source) => {
76
77
  </div>
77
78
 
78
79
  <!-- 抽屉:来源详情 -->
79
- <Teleport to="body">
80
- <Transition name="yh-slide-right">
81
- <div v-if="drawerVisible" :class="ns.e('drawer')">
82
- <div :class="ns.e('drawer-header')">
83
- <span :class="ns.e('drawer-title')">
84
- <YhIcon name="document" />
85
- {{ t("ai.sources.drawerTitle") || "\u53C2\u8003\u6765\u6E90" }}
86
- </span>
87
- <button :class="ns.e('drawer-close')" @click="drawerVisible = false">
88
- <YhIcon name="close" />
89
- </button>
90
- </div>
91
- <div :class="ns.e('drawer-content')">
92
- <div
93
- v-for="source in sources"
94
- :key="source.id"
95
- :class="[ns.e('source-card'), ns.is('active', activeSource?.id === source.id)]"
96
- @click="handleClick(source)"
97
- >
98
- <div :class="ns.e('card-header')">
99
- <div :class="ns.e('card-title-row')">
100
- <YhIcon :name="getFileIcon(source.fileType)" :class="ns.e('file-icon')" />
101
- <span :class="ns.e('card-title')">{{ source.title }}</span>
102
- </div>
103
- <div :class="ns.e('card-meta')">
104
- <span v-if="source.source" :class="ns.e('source-name')">{{
105
- source.source
106
- }}</span>
107
- <span
108
- v-if="showScore && source.score !== void 0"
109
- :class="ns.e('score-badge')"
110
- :style="{
80
+ <YhDrawer
81
+ v-model="drawerVisible"
82
+ :title="t('ai.sources.drawerTitle') || '\u53C2\u8003\u6765\u6E90'"
83
+ size="40%"
84
+ :theme-overrides="themeOverrides"
85
+ >
86
+ <template #title>
87
+ <div :class="ns.e('drawer-title-wrap')">
88
+ <YhIcon name="document" />
89
+ <span>{{ t("ai.sources.drawerTitle") || "\u53C2\u8003\u6765\u6E90" }}</span>
90
+ </div>
91
+ </template>
92
+ <div :class="ns.e('drawer-content')">
93
+ <div
94
+ v-for="source in sources"
95
+ :key="source.id"
96
+ :class="[ns.e('source-card'), ns.is('active', activeSource?.id === source.id)]"
97
+ @click="handleClick(source)"
98
+ >
99
+ <div :class="ns.e('card-header')">
100
+ <div :class="ns.e('card-title-row')">
101
+ <YhIcon :name="getFileIcon(source.fileType)" :class="ns.e('file-icon')" />
102
+ <span :class="ns.e('card-title')">{{ source.title }}</span>
103
+ </div>
104
+ <div :class="ns.e('card-meta')">
105
+ <span v-if="source.source" :class="ns.e('source-name')">{{ source.source }}</span>
106
+ <span
107
+ v-if="showScore && source.score !== void 0"
108
+ :class="ns.e('score-badge')"
109
+ :style="{
111
110
  color: scoreColor(source.score)
112
111
  }"
113
- >
114
- {{ Math.round(source.score * 100) }}%
115
- {{ t("ai.sources.relevant") || "\u76F8\u5173\u5EA6" }}
116
- </span>
117
- </div>
118
- </div>
119
- <p v-if="source.excerpt" :class="ns.e('excerpt')">{{ source.excerpt }}</p>
120
- <button
121
- v-if="source.url"
122
- :class="ns.e('open-btn')"
123
- @click="handleOpen($event, source)"
124
112
  >
125
- <YhIcon name="arrow-right" />
126
- {{ t("ai.sources.viewOriginal") || "\u67E5\u770B\u539F\u6587" }}
127
- </button>
113
+ {{ Math.round(source.score * 100) }}%
114
+ {{ t("ai.sources.relevant") || "\u76F8\u5173\u5EA6" }}
115
+ </span>
128
116
  </div>
129
117
  </div>
118
+ <p v-if="source.excerpt" :class="ns.e('excerpt')">{{ source.excerpt }}</p>
119
+ <button v-if="source.url" :class="ns.e('open-btn')" @click="handleOpen($event, source)">
120
+ <YhIcon name="arrow-right" />
121
+ {{ t("ai.sources.viewOriginal") || "\u67E5\u770B\u539F\u6587" }}
122
+ </button>
130
123
  </div>
131
- </Transition>
132
-
133
- <!-- 遮罩 -->
134
- <Transition name="yh-fade">
135
- <div
136
- v-if="drawerVisible"
137
- :class="ns.e('drawer-overlay')"
138
- @click="drawerVisible = false"
139
- ></div>
140
- </Transition>
141
- </Teleport>
124
+ </div>
125
+ </YhDrawer>
142
126
  </template>
143
127
 
144
128
  <!-- ── inline 模式:内联气泡列表 ── -->
@@ -1,7 +1,7 @@
1
1
  import { type AiSourceItem } from './ai-sources';
2
- declare var __VLS_77: {};
2
+ declare var __VLS_65: {};
3
3
  type __VLS_Slots = {} & {
4
- default?: (props: typeof __VLS_77) => any;
4
+ default?: (props: typeof __VLS_65) => any;
5
5
  };
6
6
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
7
  readonly sources: {
@@ -22,10 +22,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
22
22
  (0, _vitest.it)("should apply recording class when recording is true", () => {
23
23
  const wrapper = (0, _testUtils.mount)(_aiVoiceTrigger.default, {
24
24
  props: {
25
- recording: true
25
+ recording: true,
26
+ teleport: false
26
27
  }
27
28
  });
28
- (0, _vitest.expect)(wrapper.classes()).toContain("is-recording");
29
+ (0, _vitest.expect)(wrapper.find(".yh-ai-voice-trigger").classes()).toContain("is-recording");
29
30
  (0, _vitest.expect)(wrapper.find(".yh-ai-voice-trigger__visualizer").exists()).toBe(true);
30
31
  });
31
32
  (0, _vitest.it)("should emit start and update:recording on click when initially false", async () => {
@@ -110,4 +111,20 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
110
111
  });
111
112
  (0, _vitest.expect)(wrapper.find(".yh-ai-voice-trigger__label").text()).toBe("Custom Trigger Text");
112
113
  });
114
+ (0, _vitest.it)("should respect teleport prop", () => {
115
+ const wrapper = (0, _testUtils.mount)(_aiVoiceTrigger.default, {
116
+ props: {
117
+ variant: "sphere",
118
+ teleport: true
119
+ }
120
+ });
121
+ (0, _vitest.expect)(wrapper.props("teleport")).toBe(true);
122
+ const wrapperNoTeleport = (0, _testUtils.mount)(_aiVoiceTrigger.default, {
123
+ props: {
124
+ variant: "sphere",
125
+ teleport: false
126
+ }
127
+ });
128
+ (0, _vitest.expect)(wrapperNoTeleport.props("teleport")).toBe(false);
129
+ });
113
130
  });