@vite-plugin-opencode-assistant/components 1.0.22 → 1.0.24

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 (41) hide show
  1. package/es/index.d.ts +1 -1
  2. package/es/index.js +1 -1
  3. package/es/open-code-widget/composables/use-widget.d.ts +0 -1
  4. package/es/open-code-widget/composables/use-widget.js +0 -1
  5. package/es/open-code-widget/src/components/FloatingBubble/FloatingBubble-sfc.css +1 -0
  6. package/es/open-code-widget/src/components/FloatingBubble/FloatingBubble.vue.d.ts +44 -0
  7. package/es/open-code-widget/src/components/FloatingBubble/FloatingBubble.vue.js +290 -0
  8. package/es/open-code-widget/src/components/FloatingBubble/index.d.ts +3 -0
  9. package/es/open-code-widget/src/components/FloatingBubble/index.js +5 -0
  10. package/es/open-code-widget/src/components/FloatingBubble/types.d.ts +24 -0
  11. package/es/open-code-widget/src/components/FloatingBubble/types.js +0 -0
  12. package/es/open-code-widget/src/components/Trigger-sfc.css +1 -1
  13. package/es/open-code-widget/src/components/Trigger.vue.d.ts +21 -3
  14. package/es/open-code-widget/src/components/Trigger.vue.js +127 -48
  15. package/es/open-code-widget/src/index-sfc.css +1 -1
  16. package/es/open-code-widget/src/index.vue.d.ts +10 -11
  17. package/es/open-code-widget/src/index.vue.js +82 -17
  18. package/es/open-code-widget/src/types.d.ts +1 -2
  19. package/lib/@vite-plugin-opencode-assistant/components.cjs.js +543 -99
  20. package/lib/@vite-plugin-opencode-assistant/components.es.js +540 -96
  21. package/lib/components.css +3 -2
  22. package/lib/index.d.ts +1 -1
  23. package/lib/index.js +1 -1
  24. package/lib/open-code-widget/composables/use-widget.d.ts +0 -1
  25. package/lib/open-code-widget/composables/use-widget.js +0 -1
  26. package/lib/open-code-widget/src/components/FloatingBubble/FloatingBubble-sfc.css +1 -0
  27. package/lib/open-code-widget/src/components/FloatingBubble/FloatingBubble.vue.d.ts +44 -0
  28. package/lib/open-code-widget/src/components/FloatingBubble/FloatingBubble.vue.js +307 -0
  29. package/lib/open-code-widget/src/components/FloatingBubble/index.d.ts +3 -0
  30. package/lib/open-code-widget/src/components/FloatingBubble/index.js +35 -0
  31. package/lib/open-code-widget/src/components/FloatingBubble/types.d.ts +24 -0
  32. package/lib/open-code-widget/src/components/FloatingBubble/types.js +15 -0
  33. package/lib/open-code-widget/src/components/Trigger-sfc.css +1 -1
  34. package/lib/open-code-widget/src/components/Trigger.vue.d.ts +21 -3
  35. package/lib/open-code-widget/src/components/Trigger.vue.js +137 -48
  36. package/lib/open-code-widget/src/index-sfc.css +1 -1
  37. package/lib/open-code-widget/src/index.vue.d.ts +10 -11
  38. package/lib/open-code-widget/src/index.vue.js +80 -15
  39. package/lib/open-code-widget/src/types.d.ts +1 -2
  40. package/lib/web-types.json +1 -1
  41. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createStaticVNode, createVNode, defineComponent, inject, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, provide, ref, renderList, renderSlot, toDisplayString, toRef, useSlots, vShow, watch, withCtx, withDirectives, withModifiers } from "vue";
1
+ import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createStaticVNode, createVNode, defineComponent, inject, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, provide, ref, renderList, renderSlot, toDisplayString, toRef, useSlots, vShow, watch, withCtx, withDirectives, withModifiers } from "vue";
2
2
  import { truncate } from "@vite-plugin-opencode-assistant/shared";
3
3
  import getCssSelector from "css-selector-generator";
4
4
  //#region es/open-code-widget/src/context.js
@@ -13,24 +13,24 @@ function useOpenCodeWidgetContext() {
13
13
  }
14
14
  //#endregion
15
15
  //#region es/open-code-widget/src/components/Frame.vue.js
16
- var __defProp$1 = Object.defineProperty;
17
- var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
18
- var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
19
- var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
20
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
16
+ var __defProp$2 = Object.defineProperty;
17
+ var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols;
18
+ var __hasOwnProp$2 = Object.prototype.hasOwnProperty;
19
+ var __propIsEnum$2 = Object.prototype.propertyIsEnumerable;
20
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
21
21
  enumerable: true,
22
22
  configurable: true,
23
23
  writable: true,
24
24
  value
25
25
  }) : obj[key] = value;
26
- var __spreadValues$1 = (a, b) => {
27
- for (var prop in b || (b = {})) if (__hasOwnProp$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]);
28
- if (__getOwnPropSymbols$1) {
29
- for (var prop of __getOwnPropSymbols$1(b)) if (__propIsEnum$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]);
26
+ var __spreadValues$2 = (a, b) => {
27
+ for (var prop in b || (b = {})) if (__hasOwnProp$2.call(b, prop)) __defNormalProp$2(a, prop, b[prop]);
28
+ if (__getOwnPropSymbols$2) {
29
+ for (var prop of __getOwnPropSymbols$2(b)) if (__propIsEnum$2.call(b, prop)) __defNormalProp$2(a, prop, b[prop]);
30
30
  }
31
31
  return a;
32
32
  };
33
- var __vue_sfc__$6 = /* @__PURE__ */ defineComponent({
33
+ var __vue_sfc__$7 = /* @__PURE__ */ defineComponent({
34
34
  __name: "Frame",
35
35
  setup(__props, { expose: __expose }) {
36
36
  const iframeRef = ref(null);
@@ -38,7 +38,7 @@ var __vue_sfc__$6 = /* @__PURE__ */ defineComponent({
38
38
  function sendMessageToIframe(type, data) {
39
39
  var _a;
40
40
  if (!((_a = iframeRef.value) == null ? void 0 : _a.contentWindow)) return;
41
- iframeRef.value.contentWindow.postMessage(__spreadValues$1({ type }, data), "*");
41
+ iframeRef.value.contentWindow.postMessage(__spreadValues$2({ type }, data), "*");
42
42
  }
43
43
  onMounted(() => {
44
44
  if (iframeRef.value) iframeRef.value.addEventListener("load", () => {
@@ -68,7 +68,7 @@ var __vue_sfc__$6 = /* @__PURE__ */ defineComponent({
68
68
  var _hoisted_1$6 = { class: "opencode-iframe-container" };
69
69
  var _hoisted_2$4 = { class: "opencode-empty-state-text" };
70
70
  var _hoisted_3$4 = ["src"];
71
- function __vue_render__$6(_ctx, _cache, $props, $setup, $data, $options) {
71
+ function __vue_render__$7(_ctx, _cache, $props, $setup, $data, $options) {
72
72
  return openBlock(), createElementBlock("div", _hoisted_1$6, [
73
73
  createElementVNode("div", { class: normalizeClass(["opencode-empty-state-overlay", { visible: $setup.showEmptyState }]) }, [renderSlot(_ctx.$slots, "empty-state", {}, () => [
74
74
  _cache[1] || (_cache[1] = createElementVNode("div", { class: "opencode-empty-state-icon" }, [createElementVNode("svg", {
@@ -102,11 +102,11 @@ function __vue_render__$6(_ctx, _cache, $props, $setup, $data, $options) {
102
102
  }, null, 8, _hoisted_3$4)])
103
103
  ]);
104
104
  }
105
- __vue_sfc__$6.render = __vue_render__$6;
106
- var Frame_vue_default = __vue_sfc__$6;
105
+ __vue_sfc__$7.render = __vue_render__$7;
106
+ var Frame_vue_default = __vue_sfc__$7;
107
107
  //#endregion
108
108
  //#region es/open-code-widget/src/components/Header.vue.js
109
- var __vue_sfc__$5 = /* @__PURE__ */ defineComponent({
109
+ var __vue_sfc__$6 = /* @__PURE__ */ defineComponent({
110
110
  __name: "Header",
111
111
  setup(__props, { expose: __expose }) {
112
112
  __expose();
@@ -220,7 +220,7 @@ var _hoisted_14 = {
220
220
  "stroke-width": "2",
221
221
  "aria-hidden": "true"
222
222
  };
223
- function __vue_render__$5(_ctx, _cache, $props, $setup, $data, $options) {
223
+ function __vue_render__$6(_ctx, _cache, $props, $setup, $data, $options) {
224
224
  return openBlock(), createElementBlock("div", _hoisted_1$5, [
225
225
  createElementVNode("div", _hoisted_2$3, [
226
226
  createElementVNode("button", {
@@ -314,11 +314,11 @@ function __vue_render__$5(_ctx, _cache, $props, $setup, $data, $options) {
314
314
  ])
315
315
  ]);
316
316
  }
317
- __vue_sfc__$5.render = __vue_render__$5;
318
- var Header_vue_default = __vue_sfc__$5;
317
+ __vue_sfc__$6.render = __vue_render__$6;
318
+ var Header_vue_default = __vue_sfc__$6;
319
319
  //#endregion
320
320
  //#region es/open-code-widget/src/components/SelectHint.vue.js
321
- var __vue_sfc__$4 = /* @__PURE__ */ defineComponent({
321
+ var __vue_sfc__$5 = /* @__PURE__ */ defineComponent({
322
322
  __name: "SelectHint",
323
323
  setup(__props, { expose: __expose }) {
324
324
  __expose();
@@ -335,14 +335,14 @@ var __vue_sfc__$4 = /* @__PURE__ */ defineComponent({
335
335
  }
336
336
  });
337
337
  var _hoisted_1$4 = { class: "opencode-hint-shortcut" };
338
- function __vue_render__$4(_ctx, _cache, $props, $setup, $data, $options) {
338
+ function __vue_render__$5(_ctx, _cache, $props, $setup, $data, $options) {
339
339
  return openBlock(), createElementBlock("div", { class: normalizeClass(["opencode-select-mode-hint", { visible: $setup.visible }]) }, [_cache[0] || (_cache[0] = createElementVNode("span", null, "🎯 选择模式已开启 - 点击元素进行选择", -1)), createElementVNode("span", _hoisted_1$4, toDisplayString($setup.shortcutLabel), 1)], 2);
340
340
  }
341
- __vue_sfc__$4.render = __vue_render__$4;
342
- var SelectHint_vue_default = __vue_sfc__$4;
341
+ __vue_sfc__$5.render = __vue_render__$5;
342
+ var SelectHint_vue_default = __vue_sfc__$5;
343
343
  //#endregion
344
344
  //#region es/open-code-widget/src/components/SelectedNodes.vue.js
345
- var __vue_sfc__$3 = /* @__PURE__ */ defineComponent({
345
+ var __vue_sfc__$4 = /* @__PURE__ */ defineComponent({
346
346
  __name: "SelectedNodes",
347
347
  setup(__props, { expose: __expose }) {
348
348
  __expose();
@@ -371,7 +371,7 @@ var _hoisted_3$2 = { class: "opencode-node-content" };
371
371
  var _hoisted_4$2 = { class: "opencode-node-text" };
372
372
  var _hoisted_5$2 = { class: "opencode-node-file" };
373
373
  var _hoisted_6$2 = ["aria-label", "onClick"];
374
- function __vue_render__$3(_ctx, _cache, $props, $setup, $data, $options) {
374
+ function __vue_render__$4(_ctx, _cache, $props, $setup, $data, $options) {
375
375
  return openBlock(), createElementBlock("div", { class: normalizeClass(["opencode-right-toolbar", { collapsed: $setup.items.length === 0 }]) }, [
376
376
  _cache[1] || (_cache[1] = createElementVNode("div", { class: "opencode-selected-nodes-header" }, [createElementVNode("div", { class: "opencode-selected-nodes-title" }, "已选节点"), createElementVNode("div", { class: "opencode-selected-nodes-desc" }, "选中的节点会在对话时一起发送给助手")], -1)),
377
377
  createElementVNode("div", _hoisted_1$3, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.items, (item, index) => {
@@ -400,11 +400,11 @@ function __vue_render__$3(_ctx, _cache, $props, $setup, $data, $options) {
400
400
  }, " 一键清空 ")) : createCommentVNode("v-if", true)
401
401
  ], 2);
402
402
  }
403
- __vue_sfc__$3.render = __vue_render__$3;
404
- var SelectedNodes_vue_default = __vue_sfc__$3;
403
+ __vue_sfc__$4.render = __vue_render__$4;
404
+ var SelectedNodes_vue_default = __vue_sfc__$4;
405
405
  //#endregion
406
406
  //#region es/open-code-widget/src/components/SessionList.vue.js
407
- var __vue_sfc__$2 = /* @__PURE__ */ defineComponent({
407
+ var __vue_sfc__$3 = /* @__PURE__ */ defineComponent({
408
408
  __name: "SessionList",
409
409
  setup(__props, { expose: __expose }) {
410
410
  __expose();
@@ -469,7 +469,7 @@ var _hoisted_6$1 = { class: "opencode-session-header" };
469
469
  var _hoisted_7$1 = { class: "opencode-session-title" };
470
470
  var _hoisted_8$1 = ["aria-label", "onClick"];
471
471
  var _hoisted_9$1 = { class: "opencode-session-meta" };
472
- function __vue_render__$2(_ctx, _cache, $props, $setup, $data, $options) {
472
+ function __vue_render__$3(_ctx, _cache, $props, $setup, $data, $options) {
473
473
  return openBlock(), createElementBlock("div", { class: normalizeClass(["opencode-session-list", { collapsed: $setup.collapsed }]) }, [
474
474
  createCommentVNode(" Header "),
475
475
  !$setup.showSkeleton ? (openBlock(), createElementBlock("div", _hoisted_1$2, [_cache[1] || (_cache[1] = createElementVNode("span", { id: "opencode-session-list-title" }, "会话列表", -1)), createElementVNode("button", {
@@ -501,21 +501,377 @@ function __vue_render__$2(_ctx, _cache, $props, $setup, $data, $options) {
501
501
  }), 128)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createCommentVNode(" Empty State "), renderSlot(_ctx.$slots, "empty")], 64))])], 2112))
502
502
  ], 2);
503
503
  }
504
+ __vue_sfc__$3.render = __vue_render__$3;
505
+ var SessionList_vue_default = __vue_sfc__$3;
506
+ //#endregion
507
+ //#region es/open-code-widget/src/components/FloatingBubble/FloatingBubble.vue.js
508
+ var __defProp$1 = Object.defineProperty;
509
+ var __defProps$1 = Object.defineProperties;
510
+ var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
511
+ var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
512
+ var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
513
+ var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
514
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
515
+ enumerable: true,
516
+ configurable: true,
517
+ writable: true,
518
+ value
519
+ }) : obj[key] = value;
520
+ var __spreadValues$1 = (a, b) => {
521
+ for (var prop in b || (b = {})) if (__hasOwnProp$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]);
522
+ if (__getOwnPropSymbols$1) {
523
+ for (var prop of __getOwnPropSymbols$1(b)) if (__propIsEnum$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]);
524
+ }
525
+ return a;
526
+ };
527
+ var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
528
+ var __vue_sfc__$2 = /* @__PURE__ */ defineComponent(__spreadProps$1(__spreadValues$1({}, { name: "FloatingBubble" }), {
529
+ __name: "FloatingBubble",
530
+ props: {
531
+ offset: {
532
+ type: Object,
533
+ required: false,
534
+ default: void 0
535
+ },
536
+ axis: {
537
+ type: String,
538
+ required: false,
539
+ default: "xy"
540
+ },
541
+ magnetic: {
542
+ type: String,
543
+ required: false,
544
+ default: void 0
545
+ },
546
+ gap: {
547
+ type: [Number, Object],
548
+ required: false,
549
+ default: 24
550
+ },
551
+ teleport: {
552
+ type: null,
553
+ required: false,
554
+ default: "body"
555
+ }
556
+ },
557
+ emits: [
558
+ "update:offset",
559
+ "click",
560
+ "offset-change",
561
+ "drag-start",
562
+ "drag-end"
563
+ ],
564
+ setup(__props, { expose: __expose, emit: __emit }) {
565
+ const props = __props;
566
+ const emit = __emit;
567
+ const rootRef = ref(null);
568
+ const state = ref({
569
+ x: 0,
570
+ y: 0,
571
+ width: 0,
572
+ height: 0
573
+ });
574
+ const isObject = (val) => val !== null && typeof val === "object";
575
+ const gapX = computed(() => isObject(props.gap) ? props.gap.x : props.gap);
576
+ const gapY = computed(() => isObject(props.gap) ? props.gap.y : props.gap);
577
+ const windowWidth = ref(typeof window !== "undefined" ? window.innerWidth : 0);
578
+ const windowHeight = ref(typeof window !== "undefined" ? window.innerHeight : 0);
579
+ const boundary = computed(() => ({
580
+ top: gapY.value,
581
+ right: windowWidth.value - state.value.width - gapX.value,
582
+ bottom: windowHeight.value - state.value.height - gapY.value,
583
+ left: gapX.value
584
+ }));
585
+ const dragging = ref(false);
586
+ const initialized = ref(false);
587
+ const rootStyle = computed(() => {
588
+ const style = {};
589
+ style.transform = `translate3d(${`${state.value.x}px`}, ${`${state.value.y}px`}, 0)`;
590
+ if (dragging.value || !initialized.value) style.transition = "none";
591
+ else style.transition = "transform 0.3s ease";
592
+ return style;
593
+ });
594
+ const show = ref(true);
595
+ const updateState = () => {
596
+ if (!show.value || !rootRef.value || typeof window === "undefined") return;
597
+ const rect = rootRef.value.getBoundingClientRect();
598
+ const { offset } = props;
599
+ let x = offset ? offset.x : windowWidth.value - rect.width - gapX.value;
600
+ let y = offset ? offset.y : windowHeight.value - rect.height - gapY.value;
601
+ const maxX = windowWidth.value - rect.width - gapX.value;
602
+ const maxY = windowHeight.value - rect.height - gapY.value;
603
+ if (x < gapX.value) x = gapX.value;
604
+ if (x > maxX) x = maxX;
605
+ if (y < gapY.value) y = gapY.value;
606
+ if (y > maxY) y = maxY;
607
+ state.value = {
608
+ x,
609
+ y,
610
+ width: rect.width,
611
+ height: rect.height
612
+ };
613
+ };
614
+ const touch = {
615
+ startX: ref(0),
616
+ startY: ref(0),
617
+ deltaX: ref(0),
618
+ deltaY: ref(0),
619
+ offsetX: ref(0),
620
+ offsetY: ref(0),
621
+ isTap: ref(true),
622
+ start(e) {
623
+ this.startX.value = "touches" in e ? e.touches[0].clientX : e.clientX;
624
+ this.startY.value = "touches" in e ? e.touches[0].clientY : e.clientY;
625
+ this.deltaX.value = 0;
626
+ this.deltaY.value = 0;
627
+ this.offsetX.value = 0;
628
+ this.offsetY.value = 0;
629
+ this.isTap.value = true;
630
+ },
631
+ move(e) {
632
+ const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
633
+ const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
634
+ this.deltaX.value = clientX - this.startX.value;
635
+ this.deltaY.value = clientY - this.startY.value;
636
+ this.offsetX.value = Math.abs(this.deltaX.value);
637
+ this.offsetY.value = Math.abs(this.deltaY.value);
638
+ const TAP_OFFSET = 5;
639
+ if (this.isTap.value && (this.offsetX.value > TAP_OFFSET || this.offsetY.value > TAP_OFFSET)) this.isTap.value = false;
640
+ }
641
+ };
642
+ let prevX = 0;
643
+ let prevY = 0;
644
+ const onTouchStart = (e) => {
645
+ touch.start(e);
646
+ dragging.value = true;
647
+ prevX = state.value.x;
648
+ prevY = state.value.y;
649
+ if (!("touches" in e)) {
650
+ window.addEventListener("mousemove", onTouchMove, { passive: false });
651
+ window.addEventListener("mouseup", onTouchEnd);
652
+ }
653
+ };
654
+ const onTouchMove = (e) => {
655
+ if (e.cancelable) e.preventDefault();
656
+ const wasTap = touch.isTap.value;
657
+ touch.move(e);
658
+ if (wasTap && !touch.isTap.value) emit("drag-start");
659
+ if (props.axis === "lock") return;
660
+ if (!touch.isTap.value) {
661
+ if (props.axis === "x" || props.axis === "xy") {
662
+ let nextX = prevX + touch.deltaX.value;
663
+ if (nextX < boundary.value.left) nextX = boundary.value.left;
664
+ if (nextX > boundary.value.right) nextX = boundary.value.right;
665
+ state.value.x = nextX;
666
+ }
667
+ if (props.axis === "y" || props.axis === "xy") {
668
+ let nextY = prevY + touch.deltaY.value;
669
+ if (nextY < boundary.value.top) nextY = boundary.value.top;
670
+ if (nextY > boundary.value.bottom) nextY = boundary.value.bottom;
671
+ state.value.y = nextY;
672
+ }
673
+ emit("update:offset", {
674
+ x: state.value.x,
675
+ y: state.value.y
676
+ });
677
+ }
678
+ };
679
+ const closest = (arr, target) => {
680
+ return arr.reduce((pre, cur) => Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur);
681
+ };
682
+ const onTouchEnd = (e) => {
683
+ dragging.value = false;
684
+ if (e && !("touches" in e) && e.type === "mouseup") {
685
+ window.removeEventListener("mousemove", onTouchMove);
686
+ window.removeEventListener("mouseup", onTouchEnd);
687
+ }
688
+ requestAnimationFrame(() => {
689
+ if (props.magnetic === "x") {
690
+ const nextX = closest([boundary.value.left, boundary.value.right], state.value.x);
691
+ state.value.x = nextX;
692
+ }
693
+ if (props.magnetic === "y") {
694
+ const nextY = closest([boundary.value.top, boundary.value.bottom], state.value.y);
695
+ state.value.y = nextY;
696
+ }
697
+ if (!touch.isTap.value) {
698
+ emit("drag-end");
699
+ const offset = {
700
+ x: state.value.x,
701
+ y: state.value.y
702
+ };
703
+ emit("update:offset", offset);
704
+ if (prevX !== offset.x || prevY !== offset.y) emit("offset-change", offset);
705
+ }
706
+ });
707
+ };
708
+ const onClick = (e) => {
709
+ if (touch.isTap.value) emit("click", e);
710
+ else e.stopPropagation();
711
+ };
712
+ const handleResize = () => {
713
+ if (typeof window !== "undefined") {
714
+ windowWidth.value = window.innerWidth;
715
+ windowHeight.value = window.innerHeight;
716
+ }
717
+ };
718
+ onMounted(() => {
719
+ updateState();
720
+ nextTick(() => {
721
+ initialized.value = true;
722
+ });
723
+ if (typeof window !== "undefined") window.addEventListener("resize", handleResize);
724
+ if (rootRef.value) rootRef.value.addEventListener("touchmove", onTouchMove, { passive: false });
725
+ });
726
+ onUnmounted(() => {
727
+ if (typeof window !== "undefined") {
728
+ window.removeEventListener("resize", handleResize);
729
+ window.removeEventListener("mousemove", onTouchMove);
730
+ window.removeEventListener("mouseup", onTouchEnd);
731
+ }
732
+ if (rootRef.value) rootRef.value.removeEventListener("touchmove", onTouchMove);
733
+ });
734
+ watch([
735
+ windowWidth,
736
+ windowHeight,
737
+ gapX,
738
+ gapY,
739
+ () => props.offset
740
+ ], updateState, { deep: true });
741
+ const isOnRightSide = computed(() => {
742
+ return state.value.x > windowWidth.value / 2;
743
+ });
744
+ __expose({
745
+ isOnRightSide,
746
+ offset: computed(() => ({
747
+ x: state.value.x,
748
+ y: state.value.y
749
+ }))
750
+ });
751
+ const __returned__ = {
752
+ props,
753
+ emit,
754
+ rootRef,
755
+ state,
756
+ isObject,
757
+ gapX,
758
+ gapY,
759
+ windowWidth,
760
+ windowHeight,
761
+ boundary,
762
+ dragging,
763
+ initialized,
764
+ rootStyle,
765
+ show,
766
+ updateState,
767
+ touch,
768
+ get prevX() {
769
+ return prevX;
770
+ },
771
+ set prevX(v) {
772
+ prevX = v;
773
+ },
774
+ get prevY() {
775
+ return prevY;
776
+ },
777
+ set prevY(v) {
778
+ prevY = v;
779
+ },
780
+ onTouchStart,
781
+ onTouchMove,
782
+ closest,
783
+ onTouchEnd,
784
+ onClick,
785
+ handleResize,
786
+ isOnRightSide
787
+ };
788
+ Object.defineProperty(__returned__, "__isScriptSetup", {
789
+ enumerable: false,
790
+ value: true
791
+ });
792
+ return __returned__;
793
+ }
794
+ }));
795
+ function __vue_render__$2(_ctx, _cache, $props, $setup, $data, $options) {
796
+ return openBlock(), createBlock(Teleport, { to: $props.teleport }, [withDirectives(createElementVNode("div", {
797
+ ref: "rootRef",
798
+ class: "floating-bubble",
799
+ style: normalizeStyle($setup.rootStyle),
800
+ onTouchstartPassive: $setup.onTouchStart,
801
+ onTouchend: $setup.onTouchEnd,
802
+ onTouchcancel: $setup.onTouchEnd,
803
+ onMousedown: $setup.onTouchStart,
804
+ onClickCapture: $setup.onClick
805
+ }, [renderSlot(_ctx.$slots, "default")], 36), [[vShow, $setup.show]])], 8, ["to"]);
806
+ }
504
807
  __vue_sfc__$2.render = __vue_render__$2;
505
- var SessionList_vue_default = __vue_sfc__$2;
808
+ var FloatingBubble_vue_default = __vue_sfc__$2;
506
809
  //#endregion
507
810
  //#region es/open-code-widget/src/components/Trigger.vue.js
811
+ var STORAGE_KEY = "opencode-bubble-offset";
508
812
  var __vue_sfc__$1 = /* @__PURE__ */ defineComponent({
509
813
  __name: "Trigger",
510
- setup(__props, { expose: __expose }) {
511
- __expose();
512
- const { buttonActive: active, open, hotkeyLabel, thinking, handleToggle } = useOpenCodeWidgetContext();
814
+ emits: [
815
+ "offset-change",
816
+ "drag-start",
817
+ "drag-end"
818
+ ],
819
+ setup(__props, { expose: __expose, emit: __emit }) {
820
+ const { buttonActive: active, open, hotkeyLabel, thinking, resolvedTheme, handleToggle } = useOpenCodeWidgetContext();
821
+ const loadOffset = () => {
822
+ try {
823
+ const saved = localStorage.getItem(STORAGE_KEY);
824
+ if (saved) {
825
+ const parsed = JSON.parse(saved);
826
+ if (parsed && (parsed.x !== 0 || parsed.y !== 0)) return parsed;
827
+ }
828
+ } catch (e) {}
829
+ return {
830
+ x: 0,
831
+ y: 0
832
+ };
833
+ };
834
+ const offset = ref(loadOffset());
835
+ const emit = __emit;
836
+ const saveOffset = (value) => {
837
+ try {
838
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
839
+ } catch (e) {}
840
+ };
841
+ const handleOffsetChange = (value) => {
842
+ offset.value = value;
843
+ saveOffset(value);
844
+ emit("offset-change", value);
845
+ };
846
+ const bubbleRef = ref(null);
847
+ const isOnRightSide = computed(() => {
848
+ if (typeof window === "undefined") return true;
849
+ const centerX = window.innerWidth / 2;
850
+ return offset.value.x > centerX;
851
+ });
852
+ onMounted(() => {
853
+ if (offset.value.x !== 0 || offset.value.y !== 0) emit("offset-change", offset.value);
854
+ });
855
+ __expose({
856
+ isOnRightSide,
857
+ offset
858
+ });
513
859
  const __returned__ = {
514
860
  active,
515
861
  open,
516
862
  hotkeyLabel,
517
863
  thinking,
518
- handleToggle
864
+ resolvedTheme,
865
+ handleToggle,
866
+ STORAGE_KEY,
867
+ loadOffset,
868
+ offset,
869
+ emit,
870
+ saveOffset,
871
+ handleOffsetChange,
872
+ bubbleRef,
873
+ isOnRightSide,
874
+ FloatingBubble: FloatingBubble_vue_default
519
875
  };
520
876
  Object.defineProperty(__returned__, "__isScriptSetup", {
521
877
  enumerable: false,
@@ -526,43 +882,70 @@ var __vue_sfc__$1 = /* @__PURE__ */ defineComponent({
526
882
  });
527
883
  var _hoisted_1$1 = ["aria-expanded", "title"];
528
884
  function __vue_render__$1(_ctx, _cache, $props, $setup, $data, $options) {
529
- return openBlock(), createElementBlock("button", {
530
- class: normalizeClass(["opencode-button", {
531
- active: $setup.active,
532
- thinking: $setup.thinking
533
- }]),
534
- type: "button",
535
- "aria-expanded": $setup.open,
536
- "aria-label": "打开 AI 助手",
537
- title: `AI \u52A9\u624B (${$setup.hotkeyLabel})`,
538
- onClick: _cache[0] || (_cache[0] = (...args) => $setup.handleToggle && $setup.handleToggle(...args))
539
- }, [renderSlot(_ctx.$slots, "default", {}, () => [_cache[1] || (_cache[1] = createElementVNode("svg", {
540
- t: "1775402599580",
541
- class: "icon",
542
- viewBox: "0 0 1024 1024",
543
- version: "1.1",
544
- xmlns: "http://www.w3.org/2000/svg",
545
- "p-id": "5390",
546
- "xmlns:xlink": "http://www.w3.org/1999/xlink",
547
- width: "100%",
548
- height: "100%"
549
- }, [
550
- createElementVNode("path", {
551
- d: "M512 981.33H85.34c-15.85 0-30.38-8.77-37.77-22.81a42.624 42.624 0 0 1 2.6-44.02L135 791.08C75.25 710.5 42.67 612.6 42.67 512 42.67 253.21 253.21 42.67 512 42.67S981.34 253.21 981.34 512 770.8 981.33 512 981.33zM166.44 896H512c211.73 0 384-172.27 384-384S723.73 128 512 128 128 300.27 128 512c0 91.29 32.83 179.9 92.46 249.46 12.58 14.69 13.73 36 2.77 51.94L166.44 896z",
552
- fill: "white",
553
- "p-id": "5391"
554
- }),
555
- createElementVNode("path", {
556
- d: "M384 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z",
557
- fill: "white",
558
- "p-id": "5392"
559
- }),
560
- createElementVNode("path", {
561
- d: "M640 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z",
562
- fill: "white",
563
- "p-id": "5393"
564
- })
565
- ], -1))])], 10, _hoisted_1$1);
885
+ return openBlock(), createBlock($setup["FloatingBubble"], {
886
+ ref: "bubbleRef",
887
+ offset: $setup.offset,
888
+ "onUpdate:offset": _cache[0] || (_cache[0] = ($event) => $setup.offset = $event),
889
+ axis: "xy",
890
+ magnetic: "x",
891
+ gap: 24,
892
+ onClick: $setup.handleToggle,
893
+ onOffsetChange: $setup.handleOffsetChange,
894
+ onDragStart: _cache[1] || (_cache[1] = ($event) => $setup.emit("drag-start")),
895
+ onDragEnd: _cache[2] || (_cache[2] = ($event) => $setup.emit("drag-end"))
896
+ }, {
897
+ default: withCtx(() => [createElementVNode("button", {
898
+ class: normalizeClass(["opencode-button", {
899
+ active: $setup.active,
900
+ thinking: $setup.thinking,
901
+ "opencode-theme-dark": $setup.resolvedTheme === "dark"
902
+ }]),
903
+ type: "button",
904
+ "aria-expanded": $setup.open,
905
+ "aria-label": "打开 AI 助手",
906
+ title: `AI \u52A9\u624B (${$setup.hotkeyLabel})`
907
+ }, [renderSlot(_ctx.$slots, "default", {}, () => [_cache[3] || (_cache[3] = createElementVNode("svg", {
908
+ t: "1775402599580",
909
+ class: "icon",
910
+ viewBox: "0 0 1024 1024",
911
+ version: "1.1",
912
+ xmlns: "http://www.w3.org/2000/svg",
913
+ "p-id": "5390",
914
+ "xmlns:xlink": "http://www.w3.org/1999/xlink",
915
+ width: "100%",
916
+ height: "100%"
917
+ }, [
918
+ createElementVNode("defs", null, [createElementVNode("linearGradient", {
919
+ id: "opencode-logo-gradient",
920
+ x1: "0%",
921
+ y1: "0%",
922
+ x2: "100%",
923
+ y2: "100%"
924
+ }, [createElementVNode("stop", {
925
+ offset: "0%",
926
+ style: { "stop-color": "#667eea" }
927
+ }), createElementVNode("stop", {
928
+ offset: "100%",
929
+ style: { "stop-color": "#764ba2" }
930
+ })])]),
931
+ createElementVNode("path", {
932
+ d: "M512 981.33H85.34c-15.85 0-30.38-8.77-37.77-22.81a42.624 42.624 0 0 1 2.6-44.02L135 791.08C75.25 710.5 42.67 612.6 42.67 512 42.67 253.21 253.21 42.67 512 42.67S981.34 253.21 981.34 512 770.8 981.33 512 981.33zM166.44 896H512c211.73 0 384-172.27 384-384S723.73 128 512 128 128 300.27 128 512c0 91.29 32.83 179.9 92.46 249.46 12.58 14.69 13.73 36 2.77 51.94L166.44 896z",
933
+ fill: "url(#opencode-logo-gradient)",
934
+ "p-id": "5391"
935
+ }),
936
+ createElementVNode("path", {
937
+ d: "M384 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z",
938
+ fill: "url(#opencode-logo-gradient)",
939
+ "p-id": "5392"
940
+ }),
941
+ createElementVNode("path", {
942
+ d: "M640 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z",
943
+ fill: "url(#opencode-logo-gradient)",
944
+ "p-id": "5393"
945
+ })
946
+ ], -1))])], 10, _hoisted_1$1)]),
947
+ _: 3
948
+ }, 8, ["offset", "onClick"]);
566
949
  }
567
950
  __vue_sfc__$1.render = __vue_render__$1;
568
951
  var Trigger_vue_default = __vue_sfc__$1;
@@ -775,11 +1158,7 @@ function useWidget(options) {
775
1158
  if (options.theme.value === "auto") return systemTheme.value;
776
1159
  return options.theme.value;
777
1160
  });
778
- const containerClasses = computed(() => [
779
- "opencode-widget",
780
- options.position.value,
781
- `opencode-theme-${resolvedTheme.value}`
782
- ]);
1161
+ const containerClasses = computed(() => ["opencode-widget", `opencode-theme-${resolvedTheme.value}`]);
783
1162
  const buttonActive = computed(() => !!(options.open.value || options.selectMode.value));
784
1163
  const iframeSource = computed(() => options.iframeSrc.value || "about:blank");
785
1164
  const sessionListTitle = computed(() => options.sessionListCollapsed.value ? "展开会话列表" : "折叠会话列表");
@@ -1228,11 +1607,6 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
1228
1607
  var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({}, { name: "OpencodeWidget" }), {
1229
1608
  __name: "index",
1230
1609
  props: {
1231
- position: {
1232
- type: String,
1233
- required: false,
1234
- default: "bottom-right"
1235
- },
1236
1610
  open: {
1237
1611
  type: Boolean,
1238
1612
  required: false,
@@ -1403,6 +1777,7 @@ var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({
1403
1777
  if (dialogResolve) dialogResolve(false);
1404
1778
  };
1405
1779
  const frameRef = ref(null);
1780
+ const triggerRef = ref(null);
1406
1781
  const sendMessageToIframe = (type, data) => {
1407
1782
  var _a;
1408
1783
  (_a = frameRef.value) == null || _a.sendMessageToIframe(type, data);
@@ -1422,7 +1797,6 @@ var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({
1422
1797
  localSessionListCollapsed.value = val;
1423
1798
  });
1424
1799
  const { buttonActive, containerClasses, iframeSource, sessionListTitle, resolvedTheme, handleClose, handleEmptyAction, handleToggle, handleToggleSessionList, handleToggleTheme } = useWidget({
1425
- position: toRef(props, "position"),
1426
1800
  theme: toRef(props, "theme"),
1427
1801
  open: toRef(props, "open"),
1428
1802
  selectMode: toRef(props, "selectMode"),
@@ -1503,6 +1877,59 @@ var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({
1503
1877
  promptDockVisible.value = !promptDockVisible.value;
1504
1878
  sendMessageToIframe("prompt-dock-visibility-change", { visible: promptDockVisible.value });
1505
1879
  };
1880
+ const bubbleOffset = ref({
1881
+ x: 0,
1882
+ y: 0
1883
+ });
1884
+ const isBubbleOnRightSide = computed(() => {
1885
+ if (typeof window === "undefined") return true;
1886
+ const centerX = window.innerWidth / 2;
1887
+ return bubbleOffset.value.x > centerX;
1888
+ });
1889
+ const chatPositionStyle = computed(() => {
1890
+ if (typeof window === "undefined") return {};
1891
+ const windowWidth = window.innerWidth;
1892
+ const windowHeight = window.innerHeight;
1893
+ const chatWidth = minimized.value ? 300 : 700;
1894
+ const chatHeight = minimized.value ? 300 : Math.min(windowHeight * .86, windowHeight - 40);
1895
+ const gap = 24;
1896
+ const bubbleSize = 44;
1897
+ const screenMargin = 20;
1898
+ const style = {};
1899
+ if (isBubbleOnRightSide.value) {
1900
+ let rightPos = windowWidth - bubbleOffset.value.x + gap;
1901
+ const maxRight = windowWidth - chatWidth - screenMargin;
1902
+ if (rightPos > maxRight) rightPos = maxRight;
1903
+ style.right = `${rightPos}px`;
1904
+ style.left = "auto";
1905
+ } else {
1906
+ let leftPos = bubbleOffset.value.x + bubbleSize + gap;
1907
+ const maxLeft = windowWidth - chatWidth - screenMargin;
1908
+ if (leftPos > maxLeft) leftPos = maxLeft;
1909
+ style.left = `${leftPos}px`;
1910
+ style.right = "auto";
1911
+ }
1912
+ let bottomPos = windowHeight - bubbleOffset.value.y - bubbleSize;
1913
+ const maxBottom = windowHeight - chatHeight - screenMargin;
1914
+ if (bottomPos > maxBottom) bottomPos = maxBottom;
1915
+ if (bottomPos < screenMargin) bottomPos = screenMargin;
1916
+ style.bottom = `${bottomPos}px`;
1917
+ return style;
1918
+ });
1919
+ const handleBubbleOffsetChange = (offset) => {
1920
+ bubbleOffset.value = offset;
1921
+ };
1922
+ const isDragging = ref(false);
1923
+ let wasOpenBeforeDrag = false;
1924
+ const handleDragStart = () => {
1925
+ isDragging.value = true;
1926
+ wasOpenBeforeDrag = props.open;
1927
+ if (props.open) emit("update:open", false);
1928
+ };
1929
+ const handleDragEnd = () => {
1930
+ isDragging.value = false;
1931
+ if (wasOpenBeforeDrag) emit("update:open", true);
1932
+ };
1506
1933
  provideOpenCodeWidgetContext({
1507
1934
  theme: toRef(props, "theme"),
1508
1935
  resolvedTheme,
@@ -1574,6 +2001,7 @@ var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({
1574
2001
  handleDialogConfirm,
1575
2002
  handleDialogCancel,
1576
2003
  frameRef,
2004
+ triggerRef,
1577
2005
  sendMessageToIframe,
1578
2006
  handleFrameLoaded,
1579
2007
  localSessionListCollapsed,
@@ -1607,6 +2035,19 @@ var __vue_sfc__ = /* @__PURE__ */ defineComponent(__spreadProps(__spreadValues({
1607
2035
  tooltipContent,
1608
2036
  handleToggleMinimize,
1609
2037
  handleTogglePromptDock,
2038
+ bubbleOffset,
2039
+ isBubbleOnRightSide,
2040
+ chatPositionStyle,
2041
+ handleBubbleOffsetChange,
2042
+ isDragging,
2043
+ get wasOpenBeforeDrag() {
2044
+ return wasOpenBeforeDrag;
2045
+ },
2046
+ set wasOpenBeforeDrag(v) {
2047
+ wasOpenBeforeDrag = v;
2048
+ },
2049
+ handleDragStart,
2050
+ handleDragEnd,
1610
2051
  Frame: Frame_vue_default,
1611
2052
  Header: Header_vue_default,
1612
2053
  SelectHint: SelectHint_vue_default,
@@ -1647,16 +2088,24 @@ var _hoisted_9 = {
1647
2088
  };
1648
2089
  function __vue_render__(_ctx, _cache, $props, $setup, $data, $options) {
1649
2090
  return openBlock(), createElementBlock("div", { class: normalizeClass($setup.containerClasses) }, [
1650
- createVNode($setup["Trigger"], null, createSlots({ _: 2 }, [$setup.slots["button-icon"] ? {
2091
+ createVNode($setup["Trigger"], {
2092
+ ref: "triggerRef",
2093
+ onOffsetChange: $setup.handleBubbleOffsetChange,
2094
+ onDragStart: $setup.handleDragStart,
2095
+ onDragEnd: $setup.handleDragEnd
2096
+ }, createSlots({ _: 2 }, [$setup.slots["button-icon"] ? {
1651
2097
  name: "default",
1652
2098
  fn: withCtx(() => [renderSlot(_ctx.$slots, "button-icon")]),
1653
2099
  key: "0"
1654
- } : void 0]), 1024),
1655
- createCommentVNode(" <SelectedBubbles v-if=\"bubbleVisible\" /> "),
1656
- withDirectives(createElementVNode("div", { class: normalizeClass(["opencode-chat", {
1657
- open: $props.open,
1658
- minimized: $setup.minimized
1659
- }]) }, [
2100
+ } : void 0]), 1536),
2101
+ withDirectives(createElementVNode("div", {
2102
+ class: normalizeClass(["opencode-chat", {
2103
+ open: $props.open,
2104
+ minimized: $setup.minimized,
2105
+ dragging: $setup.isDragging
2106
+ }]),
2107
+ style: normalizeStyle($setup.chatPositionStyle)
2108
+ }, [
1660
2109
  createVNode($setup["Header"], null, createSlots({ _: 2 }, [
1661
2110
  $setup.slots["session-toggle-icon"] ? {
1662
2111
  name: "session-toggle-icon",
@@ -1674,7 +2123,6 @@ function __vue_render__(_ctx, _cache, $props, $setup, $data, $options) {
1674
2123
  key: "2"
1675
2124
  } : void 0
1676
2125
  ]), 1024),
1677
- createCommentVNode(" Notification "),
1678
2126
  $setup.notificationVisible && $setup.notificationMode === "widget" ? (openBlock(), createElementBlock("div", _hoisted_1, toDisplayString($setup.notificationMessage), 1)) : createCommentVNode("v-if", true),
1679
2127
  createElementVNode("div", _hoisted_2, [
1680
2128
  createVNode($setup["SessionList"], null, {
@@ -1705,19 +2153,16 @@ function __vue_render__(_ctx, _cache, $props, $setup, $data, $options) {
1705
2153
  ]), 1536),
1706
2154
  createVNode($setup["SelectedNodes"])
1707
2155
  ])
1708
- ], 2), [[vShow, !$props.selectMode]]),
2156
+ ], 6), [[vShow, !$props.selectMode]]),
1709
2157
  createVNode($setup["SelectHint"]),
1710
- createCommentVNode(" Inspector Highlight "),
1711
2158
  withDirectives(createElementVNode("div", {
1712
2159
  class: "opencode-element-highlight",
1713
2160
  style: normalizeStyle(__spreadValues({ display: $setup.highlightVisible ? "block" : "none" }, $setup.highlightStyle))
1714
2161
  }, null, 4), [[vShow, $setup.highlightVisible]]),
1715
- createCommentVNode(" Inspector Tooltip "),
1716
2162
  withDirectives(createElementVNode("div", {
1717
2163
  class: "opencode-element-tooltip",
1718
2164
  style: normalizeStyle(__spreadValues({ display: $setup.tooltipVisible ? "block" : "none" }, $setup.tooltipStyle))
1719
2165
  }, [createElementVNode("div", _hoisted_3, toDisplayString($setup.tooltipContent.description), 1), createElementVNode("div", _hoisted_4, toDisplayString($setup.tooltipContent.fileInfo), 1)], 4), [[vShow, $setup.tooltipVisible]]),
1720
- createCommentVNode(" Dialog "),
1721
2166
  $setup.dialogVisible ? (openBlock(), createElementBlock("div", _hoisted_5, [createElementVNode("div", _hoisted_6, [createElementVNode("div", _hoisted_7, [createElementVNode("div", _hoisted_8, toDisplayString($setup.dialogMessage), 1)]), createElementVNode("div", { class: "opencode-dialog-actions" }, [createElementVNode("button", {
1722
2167
  class: "opencode-dialog-btn cancel",
1723
2168
  onClick: $setup.handleDialogCancel
@@ -1725,7 +2170,6 @@ function __vue_render__(_ctx, _cache, $props, $setup, $data, $options) {
1725
2170
  class: "opencode-dialog-btn confirm",
1726
2171
  onClick: $setup.handleDialogConfirm
1727
2172
  }, "确认")])])])) : createCommentVNode("v-if", true),
1728
- createCommentVNode(" Page-level Notification "),
1729
2173
  (openBlock(), createBlock(Teleport, { to: "body" }, [$setup.notificationVisible && $setup.notificationMode === "page" ? (openBlock(), createElementBlock("div", _hoisted_9, toDisplayString($setup.notificationMessage), 1)) : createCommentVNode("v-if", true)]))
1730
2174
  ], 2);
1731
2175
  }
@@ -1735,7 +2179,7 @@ __vue_sfc__.render = __vue_render__;
1735
2179
  var open_code_widget_default = __vue_sfc__;
1736
2180
  //#endregion
1737
2181
  //#region es/index.js
1738
- var version = "1.0.22";
2182
+ var version = "1.0.24";
1739
2183
  function install(app, options) {
1740
2184
  [open_code_widget_default].forEach((item) => {
1741
2185
  if (item.install) app.use(item, options);