@fv-ui/ai-chat 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -34,6 +34,8 @@ type __VLS_Props$g = {
34
34
  bgColor?: string;
35
35
  width?: string;
36
36
  };
37
+ /** typing 对象形态的 interval 语义:自适应模式下的"基础节拍"(每拍最小步进 1 字的期望速率),
38
+ * 实际步进随积压放大(见 startTyping 内注释) */
37
39
  typing?: boolean | {
38
40
  step?: number;
39
41
  interval?: number;
@@ -1510,6 +1512,11 @@ type __VLS_Props$3 = {
1510
1512
  userName?: string;
1511
1513
  /** 助手名称(透传 AiMessage) */
1512
1514
  assistantName?: string;
1515
+ /** 打字机(透传 AiMessage;仅 assistant 流式期间生效,落定/历史消息整段渲染) */
1516
+ typing?: boolean | {
1517
+ step?: number;
1518
+ interval?: number;
1519
+ };
1513
1520
  };
1514
1521
  declare function scrollToBottom(smooth?: boolean): void;
1515
1522
  /** 回到顶部:与 scrollToBottom 同款 try/catch 回落(happy-dom 旧版 scrollTo(options) 可能缺实现) */
@@ -1568,6 +1575,10 @@ declare const __VLS_base$2: vue.DefineComponent<__VLS_Props$3, {
1568
1575
  "onBranch-change"?: ((dir: BranchDirection, message: MessageNode) => any) | undefined;
1569
1576
  }>, {
1570
1577
  name: string;
1578
+ typing: boolean | {
1579
+ step?: number;
1580
+ interval?: number;
1581
+ };
1571
1582
  icon: string | Component;
1572
1583
  nodes: Record<string, MessageNode>;
1573
1584
  description: string;
@@ -1650,6 +1661,12 @@ type __VLS_Props$2 = {
1650
1661
  placeholder?: string;
1651
1662
  /** 工具名 → 步骤展示名(透传 useChat toolStepLabel,流式工具轨迹步骤的 title) */
1652
1663
  toolStepLabel?: (toolName: string) => string;
1664
+ /** 打字机(透传 AiMessages;仅 assistant 流式期间逐字平滑打出,
1665
+ * 落定/历史消息整段渲染;true 用默认节奏,对象形态自定义 step/interval) */
1666
+ typing?: boolean | {
1667
+ step?: number;
1668
+ interval?: number;
1669
+ };
1653
1670
  /** 输入区阴影效果(透传 Sender;关闭传 false) */
1654
1671
  senderShadow?: boolean;
1655
1672
  /** 空态建议列表 */
@@ -1752,6 +1769,10 @@ declare const __VLS_base$1: vue.DefineComponent<__VLS_Props$2, {
1752
1769
  }) => any) | undefined;
1753
1770
  }>, {
1754
1771
  title: string;
1772
+ typing: boolean | {
1773
+ step?: number;
1774
+ interval?: number;
1775
+ };
1755
1776
  placeholder: string;
1756
1777
  accept: string;
1757
1778
  baseURL: string;
@@ -1823,6 +1844,11 @@ type __VLS_Props$1 = {
1823
1844
  assistantName?: string;
1824
1845
  /** Thinking autoCollapse 透传 */
1825
1846
  autoCollapse?: boolean;
1847
+ /** 打字机(透传 Typewriter;仅 assistant 流式期间生效,落定/历史消息整段渲染) */
1848
+ typing?: boolean | {
1849
+ step?: number;
1850
+ interval?: number;
1851
+ };
1826
1852
  };
1827
1853
  declare var __VLS_1: {
1828
1854
  message: MessageNode;
@@ -1839,7 +1865,7 @@ declare var __VLS_33: {
1839
1865
  message: MessageNode;
1840
1866
  files: ChatFile[];
1841
1867
  };
1842
- declare var __VLS_57: {
1868
+ declare var __VLS_67: {
1843
1869
  message: MessageNode;
1844
1870
  };
1845
1871
  type __VLS_Slots = {} & {
@@ -1855,7 +1881,7 @@ type __VLS_Slots = {} & {
1855
1881
  } & {
1856
1882
  'message-files'?: (props: typeof __VLS_33) => any;
1857
1883
  } & {
1858
- 'message-actions'?: (props: typeof __VLS_57) => any;
1884
+ 'message-actions'?: (props: typeof __VLS_67) => any;
1859
1885
  };
1860
1886
  declare const __VLS_base: vue.DefineComponent<__VLS_Props$1, {
1861
1887
  message: vue.ComputedRef<MessageNode>;
@@ -1868,6 +1894,10 @@ declare const __VLS_base: vue.DefineComponent<__VLS_Props$1, {
1868
1894
  onRegenerate?: ((message: MessageNode) => any) | undefined;
1869
1895
  "onBranch-change"?: ((dir: BranchDirection, message: MessageNode) => any) | undefined;
1870
1896
  }>, {
1897
+ typing: boolean | {
1898
+ step?: number;
1899
+ interval?: number;
1900
+ };
1871
1901
  autoCollapse: boolean;
1872
1902
  branch: AiMessageBranchInfo;
1873
1903
  showActions: boolean;
package/es/index.mjs CHANGED
@@ -1595,7 +1595,9 @@ var _sfc_main$h = /* @__PURE__ */ defineComponent({
1595
1595
  const step = (typeof props.typing === "object" ? props.typing.step : void 0) ?? 2;
1596
1596
  const interval = (typeof props.typing === "object" ? props.typing.interval : void 0) ?? 50;
1597
1597
  timer = setInterval(() => {
1598
- cursor = Math.min(props.content.length, cursor + step);
1598
+ const backlog = props.content.length - cursor;
1599
+ const speed = Math.min(step * 6, Math.max(1, Math.round(step * (1 + backlog / 8))));
1600
+ cursor = Math.min(props.content.length, cursor + speed);
1599
1601
  renderedContent.value = props.content.slice(0, cursor);
1600
1602
  progress.value = props.content.length ? cursor / props.content.length : 1;
1601
1603
  emit("writing");
@@ -6049,7 +6051,7 @@ const _hoisted_12 = {
6049
6051
  class: "fv-msg-error"
6050
6052
  };
6051
6053
  const _hoisted_13 = {
6052
- key: 2,
6054
+ key: 3,
6053
6055
  class: "fv-msg-pending"
6054
6056
  };
6055
6057
  const _hoisted_14 = {
@@ -6081,7 +6083,8 @@ var _sfc_main$3 = /* @__PURE__ */ defineComponent({
6081
6083
  assistantAvatar: { type: String, required: false, default: "" },
6082
6084
  userName: { type: String, required: false, default: "" },
6083
6085
  assistantName: { type: String, required: false, default: "" },
6084
- autoCollapse: { type: Boolean, required: false, default: true }
6086
+ autoCollapse: { type: Boolean, required: false, default: true },
6087
+ typing: { type: [Boolean, Object], required: false, default: false }
6085
6088
  },
6086
6089
  emits: ["copy", "regenerate", "branch-change"],
6087
6090
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -6096,6 +6099,15 @@ var _sfc_main$3 = /* @__PURE__ */ defineComponent({
6096
6099
  });
6097
6100
  const hasReasoning = computed(() => !!props.message.reasoning);
6098
6101
  const hasToolSteps = computed(() => (props.message.toolSteps?.length ?? 0) > 0);
6102
+ const wasStreaming = ref(false);
6103
+ watch(
6104
+ () => props.message.status,
6105
+ (s) => {
6106
+ if (s === "pending" || s === "streaming") wasStreaming.value = true;
6107
+ },
6108
+ { immediate: true }
6109
+ );
6110
+ const useTypewriter = computed(() => !isUser.value && !!props.typing && wasStreaming.value);
6099
6111
  const toolChainItems = computed(() => {
6100
6112
  const steps = props.message.toolSteps ?? [];
6101
6113
  const running = props.message.status === "pending" || props.message.status === "streaming";
@@ -6231,7 +6243,7 @@ var _sfc_main$3 = /* @__PURE__ */ defineComponent({
6231
6243
  class: "fv-msg-tools",
6232
6244
  items: toolChainItems.value
6233
6245
  }, null, 8, ["items"])) : createCommentVNode("v-if", true),
6234
- createCommentVNode(" \u601D\u8003\u94FE\u6298\u53E0(assistant \u542B reasoning \u65F6) "),
6246
+ createCommentVNode(" \u601D\u8003\u94FE\u6298\u53E0(assistant \u542B reasoning \u65F6);\u6D41\u5F0F\u671F\u95F4\u7ECF\u6253\u5B57\u673A\u5E73\u6ED1\u6253\u51FA "),
6235
6247
  !isUser.value && hasReasoning.value ? (openBlock(), createBlock(_sfc_main$a, {
6236
6248
  key: 3,
6237
6249
  content: __props.message.reasoning,
@@ -6239,9 +6251,15 @@ var _sfc_main$3 = /* @__PURE__ */ defineComponent({
6239
6251
  "auto-collapse": __props.autoCollapse
6240
6252
  }, {
6241
6253
  content: withCtx(() => [
6242
- createVNode(_sfc_main$i, {
6254
+ useTypewriter.value ? (openBlock(), createBlock(_sfc_main$h, {
6255
+ key: 0,
6256
+ content: __props.message.reasoning ?? "",
6257
+ "is-markdown": "",
6258
+ typing: __props.typing
6259
+ }, null, 8, ["content", "typing"])) : (openBlock(), createBlock(_sfc_main$i, {
6260
+ key: 1,
6243
6261
  content: __props.message.reasoning ?? ""
6244
- }, null, 8, ["content"])
6262
+ }, null, 8, ["content"]))
6245
6263
  ]),
6246
6264
  _: 1
6247
6265
  /* STABLE */
@@ -6254,8 +6272,13 @@ var _sfc_main$3 = /* @__PURE__ */ defineComponent({
6254
6272
  toDisplayString(__props.message.error || "\u751F\u6210\u5931\u8D25"),
6255
6273
  1
6256
6274
  /* TEXT */
6257
- )) : __props.message.content ? (openBlock(), createBlock(_sfc_main$i, {
6275
+ )) : useTypewriter.value && __props.message.content ? (openBlock(), createBlock(_sfc_main$h, {
6258
6276
  key: 1,
6277
+ content: __props.message.content,
6278
+ "is-markdown": "",
6279
+ typing: __props.typing
6280
+ }, null, 8, ["content", "typing"])) : __props.message.content ? (openBlock(), createBlock(_sfc_main$i, {
6281
+ key: 2,
6259
6282
  content: __props.message.content
6260
6283
  }, null, 8, ["content"])) : __props.message.status === "pending" || __props.message.status === "streaming" ? (openBlock(), createElementBlock("span", _hoisted_13, [
6261
6284
  (openBlock(), createElementBlock(
@@ -6462,7 +6485,8 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6462
6485
  userAvatar: { type: String, required: false, default: "" },
6463
6486
  assistantAvatar: { type: String, required: false, default: "" },
6464
6487
  userName: { type: String, required: false, default: "" },
6465
- assistantName: { type: String, required: false, default: "" }
6488
+ assistantName: { type: String, required: false, default: "" },
6489
+ typing: { type: [Boolean, Object], required: false, default: false }
6466
6490
  },
6467
6491
  emits: ["select", "copy", "regenerate", "branch-change"],
6468
6492
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -6615,6 +6639,7 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6615
6639
  "assistant-avatar": __props.assistantAvatar,
6616
6640
  "user-name": __props.userName,
6617
6641
  "assistant-name": __props.assistantName,
6642
+ typing: __props.typing,
6618
6643
  onCopy,
6619
6644
  onRegenerate,
6620
6645
  onBranchChange: onBranch
@@ -6664,7 +6689,7 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6664
6689
  ]),
6665
6690
  key: "5"
6666
6691
  } : void 0
6667
- ]), 1032, ["message", "branch", "show-actions", "show-branch-switch", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name"]);
6692
+ ]), 1032, ["message", "branch", "show-actions", "show-branch-switch", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name", "typing"]);
6668
6693
  }),
6669
6694
  128
6670
6695
  /* KEYED_FRAGMENT */
@@ -7002,6 +7027,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
7002
7027
  assistantName: { type: String, required: false, default: "" },
7003
7028
  placeholder: { type: String, required: false, default: "" },
7004
7029
  toolStepLabel: { type: Function, required: false, default: void 0 },
7030
+ typing: { type: [Boolean, Object], required: false, default: false },
7005
7031
  senderShadow: { type: Boolean, required: false, default: true },
7006
7032
  suggestions: { type: Array, required: false, default: () => [] },
7007
7033
  features: { type: Object, required: false, default: () => ({}) },
@@ -7275,6 +7301,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
7275
7301
  "assistant-avatar": __props.assistantAvatar,
7276
7302
  "user-name": __props.userName,
7277
7303
  "assistant-name": __props.assistantName,
7304
+ typing: __props.typing,
7278
7305
  onSelect: onSelectSuggestion,
7279
7306
  onCopy,
7280
7307
  onRegenerate,
@@ -7346,7 +7373,7 @@ var _sfc_main = /* @__PURE__ */ defineComponent({
7346
7373
  ]),
7347
7374
  key: "8"
7348
7375
  } : void 0
7349
- ]), 1032, ["messages", "nodes", "name", "suggestions", "show-scroll-button", "scroll-button-threshold", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name"]),
7376
+ ]), 1032, ["messages", "nodes", "name", "suggestions", "show-scroll-button", "scroll-button-threshold", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name", "typing"]),
7350
7377
  __props.features?.artifacts && artifacts.value.length ? (openBlock(), createBlock(_sfc_main$1, {
7351
7378
  key: 0,
7352
7379
  artifacts: artifacts.value,
package/lib/index.js CHANGED
@@ -1611,7 +1611,9 @@
1611
1611
  const step = (typeof props.typing === "object" ? props.typing.step : void 0) ?? 2;
1612
1612
  const interval = (typeof props.typing === "object" ? props.typing.interval : void 0) ?? 50;
1613
1613
  timer = setInterval(() => {
1614
- cursor = Math.min(props.content.length, cursor + step);
1614
+ const backlog = props.content.length - cursor;
1615
+ const speed = Math.min(step * 6, Math.max(1, Math.round(step * (1 + backlog / 8))));
1616
+ cursor = Math.min(props.content.length, cursor + speed);
1615
1617
  renderedContent.value = props.content.slice(0, cursor);
1616
1618
  progress.value = props.content.length ? cursor / props.content.length : 1;
1617
1619
  emit("writing");
@@ -6065,7 +6067,7 @@ ${fileParts.join("\n")}` });
6065
6067
  class: "fv-msg-error"
6066
6068
  };
6067
6069
  const _hoisted_13 = {
6068
- key: 2,
6070
+ key: 3,
6069
6071
  class: "fv-msg-pending"
6070
6072
  };
6071
6073
  const _hoisted_14 = {
@@ -6097,7 +6099,8 @@ ${fileParts.join("\n")}` });
6097
6099
  assistantAvatar: { type: String, required: false, default: "" },
6098
6100
  userName: { type: String, required: false, default: "" },
6099
6101
  assistantName: { type: String, required: false, default: "" },
6100
- autoCollapse: { type: Boolean, required: false, default: true }
6102
+ autoCollapse: { type: Boolean, required: false, default: true },
6103
+ typing: { type: [Boolean, Object], required: false, default: false }
6101
6104
  },
6102
6105
  emits: ["copy", "regenerate", "branch-change"],
6103
6106
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -6112,6 +6115,15 @@ ${fileParts.join("\n")}` });
6112
6115
  });
6113
6116
  const hasReasoning = vue.computed(() => !!props.message.reasoning);
6114
6117
  const hasToolSteps = vue.computed(() => (props.message.toolSteps?.length ?? 0) > 0);
6118
+ const wasStreaming = vue.ref(false);
6119
+ vue.watch(
6120
+ () => props.message.status,
6121
+ (s) => {
6122
+ if (s === "pending" || s === "streaming") wasStreaming.value = true;
6123
+ },
6124
+ { immediate: true }
6125
+ );
6126
+ const useTypewriter = vue.computed(() => !isUser.value && !!props.typing && wasStreaming.value);
6115
6127
  const toolChainItems = vue.computed(() => {
6116
6128
  const steps = props.message.toolSteps ?? [];
6117
6129
  const running = props.message.status === "pending" || props.message.status === "streaming";
@@ -6247,7 +6259,7 @@ ${fileParts.join("\n")}` });
6247
6259
  class: "fv-msg-tools",
6248
6260
  items: toolChainItems.value
6249
6261
  }, null, 8, ["items"])) : vue.createCommentVNode("v-if", true),
6250
- vue.createCommentVNode(" \u601D\u8003\u94FE\u6298\u53E0(assistant \u542B reasoning \u65F6) "),
6262
+ vue.createCommentVNode(" \u601D\u8003\u94FE\u6298\u53E0(assistant \u542B reasoning \u65F6);\u6D41\u5F0F\u671F\u95F4\u7ECF\u6253\u5B57\u673A\u5E73\u6ED1\u6253\u51FA "),
6251
6263
  !isUser.value && hasReasoning.value ? (vue.openBlock(), vue.createBlock(_sfc_main$a, {
6252
6264
  key: 3,
6253
6265
  content: __props.message.reasoning,
@@ -6255,9 +6267,15 @@ ${fileParts.join("\n")}` });
6255
6267
  "auto-collapse": __props.autoCollapse
6256
6268
  }, {
6257
6269
  content: vue.withCtx(() => [
6258
- vue.createVNode(_sfc_main$i, {
6270
+ useTypewriter.value ? (vue.openBlock(), vue.createBlock(_sfc_main$h, {
6271
+ key: 0,
6272
+ content: __props.message.reasoning ?? "",
6273
+ "is-markdown": "",
6274
+ typing: __props.typing
6275
+ }, null, 8, ["content", "typing"])) : (vue.openBlock(), vue.createBlock(_sfc_main$i, {
6276
+ key: 1,
6259
6277
  content: __props.message.reasoning ?? ""
6260
- }, null, 8, ["content"])
6278
+ }, null, 8, ["content"]))
6261
6279
  ]),
6262
6280
  _: 1
6263
6281
  /* STABLE */
@@ -6270,8 +6288,13 @@ ${fileParts.join("\n")}` });
6270
6288
  vue.toDisplayString(__props.message.error || "\u751F\u6210\u5931\u8D25"),
6271
6289
  1
6272
6290
  /* TEXT */
6273
- )) : __props.message.content ? (vue.openBlock(), vue.createBlock(_sfc_main$i, {
6291
+ )) : useTypewriter.value && __props.message.content ? (vue.openBlock(), vue.createBlock(_sfc_main$h, {
6274
6292
  key: 1,
6293
+ content: __props.message.content,
6294
+ "is-markdown": "",
6295
+ typing: __props.typing
6296
+ }, null, 8, ["content", "typing"])) : __props.message.content ? (vue.openBlock(), vue.createBlock(_sfc_main$i, {
6297
+ key: 2,
6275
6298
  content: __props.message.content
6276
6299
  }, null, 8, ["content"])) : __props.message.status === "pending" || __props.message.status === "streaming" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_13, [
6277
6300
  (vue.openBlock(), vue.createElementBlock(
@@ -6478,7 +6501,8 @@ ${fileParts.join("\n")}` });
6478
6501
  userAvatar: { type: String, required: false, default: "" },
6479
6502
  assistantAvatar: { type: String, required: false, default: "" },
6480
6503
  userName: { type: String, required: false, default: "" },
6481
- assistantName: { type: String, required: false, default: "" }
6504
+ assistantName: { type: String, required: false, default: "" },
6505
+ typing: { type: [Boolean, Object], required: false, default: false }
6482
6506
  },
6483
6507
  emits: ["select", "copy", "regenerate", "branch-change"],
6484
6508
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -6631,6 +6655,7 @@ ${fileParts.join("\n")}` });
6631
6655
  "assistant-avatar": __props.assistantAvatar,
6632
6656
  "user-name": __props.userName,
6633
6657
  "assistant-name": __props.assistantName,
6658
+ typing: __props.typing,
6634
6659
  onCopy,
6635
6660
  onRegenerate,
6636
6661
  onBranchChange: onBranch
@@ -6680,7 +6705,7 @@ ${fileParts.join("\n")}` });
6680
6705
  ]),
6681
6706
  key: "5"
6682
6707
  } : void 0
6683
- ]), 1032, ["message", "branch", "show-actions", "show-branch-switch", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name"]);
6708
+ ]), 1032, ["message", "branch", "show-actions", "show-branch-switch", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name", "typing"]);
6684
6709
  }),
6685
6710
  128
6686
6711
  /* KEYED_FRAGMENT */
@@ -7018,6 +7043,7 @@ ${fileParts.join("\n")}` });
7018
7043
  assistantName: { type: String, required: false, default: "" },
7019
7044
  placeholder: { type: String, required: false, default: "" },
7020
7045
  toolStepLabel: { type: Function, required: false, default: void 0 },
7046
+ typing: { type: [Boolean, Object], required: false, default: false },
7021
7047
  senderShadow: { type: Boolean, required: false, default: true },
7022
7048
  suggestions: { type: Array, required: false, default: () => [] },
7023
7049
  features: { type: Object, required: false, default: () => ({}) },
@@ -7291,6 +7317,7 @@ ${fileParts.join("\n")}` });
7291
7317
  "assistant-avatar": __props.assistantAvatar,
7292
7318
  "user-name": __props.userName,
7293
7319
  "assistant-name": __props.assistantName,
7320
+ typing: __props.typing,
7294
7321
  onSelect: onSelectSuggestion,
7295
7322
  onCopy,
7296
7323
  onRegenerate,
@@ -7362,7 +7389,7 @@ ${fileParts.join("\n")}` });
7362
7389
  ]),
7363
7390
  key: "8"
7364
7391
  } : void 0
7365
- ]), 1032, ["messages", "nodes", "name", "suggestions", "show-scroll-button", "scroll-button-threshold", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name"]),
7392
+ ]), 1032, ["messages", "nodes", "name", "suggestions", "show-scroll-button", "scroll-button-threshold", "show-user-avatar", "show-user-name", "show-assistant-avatar", "show-assistant-name", "user-avatar", "assistant-avatar", "user-name", "assistant-name", "typing"]),
7366
7393
  __props.features?.artifacts && artifacts.value.length ? (vue.openBlock(), vue.createBlock(_sfc_main$1, {
7367
7394
  key: 0,
7368
7395
  artifacts: artifacts.value,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fv-ui/ai-chat",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "基于 Vue3 + Element Plus 的 AI 对话组件库",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",