@fv-ui/ai-chat 0.7.0 → 0.8.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
@@ -1484,7 +1484,7 @@ type __VLS_Props$3 = {
1484
1484
  suggestions?: AiMessagesSuggestion[];
1485
1485
  /** 空态建议区标题 */
1486
1486
  suggestionsTitle?: string;
1487
- /** 用户上滚免打扰阈值(px):距底不超过该值才自动吸底 */
1487
+ /** 用户上滚免打扰阈值(px):距底不超过该值视为「钉在底部」,流式/完成自动跟随 */
1488
1488
  scrollThreshold?: number;
1489
1489
  /** 显示滚到底部悬浮按钮 */
1490
1490
  showScrollButton?: boolean;
package/es/index.mjs CHANGED
@@ -6450,7 +6450,7 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6450
6450
  description: { type: String, required: false, default: "" },
6451
6451
  suggestions: { type: Array, required: false, default: () => [] },
6452
6452
  suggestionsTitle: { type: String, required: false, default: "" },
6453
- scrollThreshold: { type: Number, required: false, default: 40 },
6453
+ scrollThreshold: { type: Number, required: false, default: 100 },
6454
6454
  showScrollButton: { type: Boolean, required: false, default: true },
6455
6455
  scrollButtonThreshold: { type: Number, required: false, default: 100 },
6456
6456
  showActions: { type: Boolean, required: false, default: true },
@@ -6475,21 +6475,35 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6475
6475
  if (!el) return 0;
6476
6476
  return el.scrollHeight - el.scrollTop - el.clientHeight;
6477
6477
  }
6478
+ const isPinnedToBottom = ref(true);
6478
6479
  function scrollToBottom(smooth = true) {
6479
6480
  const el = scrollRef.value;
6480
6481
  if (!el) return;
6482
+ isPinnedToBottom.value = true;
6481
6483
  try {
6482
6484
  el.scrollTo({ top: el.scrollHeight, behavior: smooth ? "smooth" : "auto" });
6483
6485
  } catch {
6484
6486
  el.scrollTop = el.scrollHeight;
6485
6487
  }
6486
6488
  }
6487
- function scrollToBottomIfNear() {
6488
- if (distanceFromBottom() <= props.scrollThreshold) nextTick(() => scrollToBottom());
6489
+ function scrollToBottomNow() {
6490
+ const el = scrollRef.value;
6491
+ if (!el) return;
6492
+ isPinnedToBottom.value = true;
6493
+ try {
6494
+ el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
6495
+ } catch {
6496
+ el.scrollTop = el.scrollHeight;
6497
+ }
6498
+ }
6499
+ function followIfPinned() {
6500
+ if (isPinnedToBottom.value) nextTick(() => scrollToBottomNow());
6489
6501
  }
6490
6502
  const isAwayFromBottom = ref(false);
6491
6503
  function onScroll() {
6492
- isAwayFromBottom.value = distanceFromBottom() > props.scrollButtonThreshold;
6504
+ const d = distanceFromBottom();
6505
+ isPinnedToBottom.value = d <= props.scrollThreshold;
6506
+ isAwayFromBottom.value = d > props.scrollButtonThreshold;
6493
6507
  }
6494
6508
  watch(() => props.scrollButtonThreshold, onScroll);
6495
6509
  onMounted(() => {
@@ -6497,7 +6511,7 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6497
6511
  onScroll();
6498
6512
  });
6499
6513
  watch(() => props.messages.length, (nv, ov) => {
6500
- if (nv > (ov ?? 0)) scrollToBottomIfNear();
6514
+ if (nv > (ov ?? 0)) followIfPinned();
6501
6515
  });
6502
6516
  watch(
6503
6517
  () => props.messages.filter((m) => m.role === "user").length,
@@ -6506,8 +6520,15 @@ var _sfc_main$2 = /* @__PURE__ */ defineComponent({
6506
6520
  }
6507
6521
  );
6508
6522
  watch(
6509
- () => props.messages.reduce((sum, m) => sum + m.content.length + (m.reasoning?.length ?? 0), 0),
6510
- () => scrollToBottomIfNear()
6523
+ () => props.messages.reduce(
6524
+ (sum, m) => sum + m.content.length + (m.reasoning?.length ?? 0) + (m.toolSteps?.reduce((s, t) => s + (t.status?.length ?? 0) + (t.title?.length ?? 0), 0) ?? 0) + (m.toolSteps?.length ?? 0),
6525
+ 0
6526
+ ),
6527
+ () => followIfPinned()
6528
+ );
6529
+ watch(
6530
+ () => props.messages.reduce((sum, m) => sum + (m.status === "done" || m.status === "error" ? 1 : 0), 0),
6531
+ () => followIfPinned()
6511
6532
  );
6512
6533
  function branchInfo(m) {
6513
6534
  if (!m) return void 0;
package/lib/index.js CHANGED
@@ -6466,7 +6466,7 @@ ${fileParts.join("\n")}` });
6466
6466
  description: { type: String, required: false, default: "" },
6467
6467
  suggestions: { type: Array, required: false, default: () => [] },
6468
6468
  suggestionsTitle: { type: String, required: false, default: "" },
6469
- scrollThreshold: { type: Number, required: false, default: 40 },
6469
+ scrollThreshold: { type: Number, required: false, default: 100 },
6470
6470
  showScrollButton: { type: Boolean, required: false, default: true },
6471
6471
  scrollButtonThreshold: { type: Number, required: false, default: 100 },
6472
6472
  showActions: { type: Boolean, required: false, default: true },
@@ -6491,21 +6491,35 @@ ${fileParts.join("\n")}` });
6491
6491
  if (!el) return 0;
6492
6492
  return el.scrollHeight - el.scrollTop - el.clientHeight;
6493
6493
  }
6494
+ const isPinnedToBottom = vue.ref(true);
6494
6495
  function scrollToBottom(smooth = true) {
6495
6496
  const el = scrollRef.value;
6496
6497
  if (!el) return;
6498
+ isPinnedToBottom.value = true;
6497
6499
  try {
6498
6500
  el.scrollTo({ top: el.scrollHeight, behavior: smooth ? "smooth" : "auto" });
6499
6501
  } catch {
6500
6502
  el.scrollTop = el.scrollHeight;
6501
6503
  }
6502
6504
  }
6503
- function scrollToBottomIfNear() {
6504
- if (distanceFromBottom() <= props.scrollThreshold) vue.nextTick(() => scrollToBottom());
6505
+ function scrollToBottomNow() {
6506
+ const el = scrollRef.value;
6507
+ if (!el) return;
6508
+ isPinnedToBottom.value = true;
6509
+ try {
6510
+ el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
6511
+ } catch {
6512
+ el.scrollTop = el.scrollHeight;
6513
+ }
6514
+ }
6515
+ function followIfPinned() {
6516
+ if (isPinnedToBottom.value) vue.nextTick(() => scrollToBottomNow());
6505
6517
  }
6506
6518
  const isAwayFromBottom = vue.ref(false);
6507
6519
  function onScroll() {
6508
- isAwayFromBottom.value = distanceFromBottom() > props.scrollButtonThreshold;
6520
+ const d = distanceFromBottom();
6521
+ isPinnedToBottom.value = d <= props.scrollThreshold;
6522
+ isAwayFromBottom.value = d > props.scrollButtonThreshold;
6509
6523
  }
6510
6524
  vue.watch(() => props.scrollButtonThreshold, onScroll);
6511
6525
  vue.onMounted(() => {
@@ -6513,7 +6527,7 @@ ${fileParts.join("\n")}` });
6513
6527
  onScroll();
6514
6528
  });
6515
6529
  vue.watch(() => props.messages.length, (nv, ov) => {
6516
- if (nv > (ov ?? 0)) scrollToBottomIfNear();
6530
+ if (nv > (ov ?? 0)) followIfPinned();
6517
6531
  });
6518
6532
  vue.watch(
6519
6533
  () => props.messages.filter((m) => m.role === "user").length,
@@ -6522,8 +6536,15 @@ ${fileParts.join("\n")}` });
6522
6536
  }
6523
6537
  );
6524
6538
  vue.watch(
6525
- () => props.messages.reduce((sum, m) => sum + m.content.length + (m.reasoning?.length ?? 0), 0),
6526
- () => scrollToBottomIfNear()
6539
+ () => props.messages.reduce(
6540
+ (sum, m) => sum + m.content.length + (m.reasoning?.length ?? 0) + (m.toolSteps?.reduce((s, t) => s + (t.status?.length ?? 0) + (t.title?.length ?? 0), 0) ?? 0) + (m.toolSteps?.length ?? 0),
6541
+ 0
6542
+ ),
6543
+ () => followIfPinned()
6544
+ );
6545
+ vue.watch(
6546
+ () => props.messages.reduce((sum, m) => sum + (m.status === "done" || m.status === "error" ? 1 : 0), 0),
6547
+ () => followIfPinned()
6527
6548
  );
6528
6549
  function branchInfo(m) {
6529
6550
  if (!m) return void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fv-ui/ai-chat",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "基于 Vue3 + Element Plus 的 AI 对话组件库",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",