@aipanel/ui 1.2.0-beta.0

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 (153) hide show
  1. package/es/AI-panel-widget/composables/use-inspector.d.ts +52 -0
  2. package/es/AI-panel-widget/composables/use-inspector.mjs +437 -0
  3. package/es/AI-panel-widget/composables/use-persist-state.d.ts +32 -0
  4. package/es/AI-panel-widget/composables/use-persist-state.mjs +83 -0
  5. package/es/AI-panel-widget/composables/use-selection.d.ts +19 -0
  6. package/es/AI-panel-widget/composables/use-selection.mjs +112 -0
  7. package/es/AI-panel-widget/composables/use-session.d.ts +16 -0
  8. package/es/AI-panel-widget/composables/use-session.mjs +47 -0
  9. package/es/AI-panel-widget/composables/use-split.d.ts +30 -0
  10. package/es/AI-panel-widget/composables/use-split.mjs +133 -0
  11. package/es/AI-panel-widget/composables/use-widget.d.ts +27 -0
  12. package/es/AI-panel-widget/composables/use-widget.mjs +78 -0
  13. package/es/AI-panel-widget/index.d.ts +5 -0
  14. package/es/AI-panel-widget/index.mjs +9 -0
  15. package/es/AI-panel-widget/src/assets/logo.svg +13 -0
  16. package/es/AI-panel-widget/src/components/AIPanelLogo-sfc.css +1 -0
  17. package/es/AI-panel-widget/src/components/AIPanelLogo.vue.d.ts +8 -0
  18. package/es/AI-panel-widget/src/components/AIPanelLogo.vue.mjs +40 -0
  19. package/es/AI-panel-widget/src/components/ChatPanel-sfc.css +1 -0
  20. package/es/AI-panel-widget/src/components/ChatPanel.vue.d.ts +179 -0
  21. package/es/AI-panel-widget/src/components/ChatPanel.vue.mjs +308 -0
  22. package/es/AI-panel-widget/src/components/FloatingBubble/FloatingBubble-sfc.css +1 -0
  23. package/es/AI-panel-widget/src/components/FloatingBubble/FloatingBubble.vue.d.ts +44 -0
  24. package/es/AI-panel-widget/src/components/FloatingBubble/FloatingBubble.vue.mjs +311 -0
  25. package/es/AI-panel-widget/src/components/FloatingBubble/index.d.ts +3 -0
  26. package/es/AI-panel-widget/src/components/FloatingBubble/index.mjs +5 -0
  27. package/es/AI-panel-widget/src/components/FloatingBubble/types.d.ts +24 -0
  28. package/es/AI-panel-widget/src/components/FloatingBubble/types.mjs +0 -0
  29. package/es/AI-panel-widget/src/components/Frame-sfc.css +1 -0
  30. package/es/AI-panel-widget/src/components/Frame.vue.d.ts +22 -0
  31. package/es/AI-panel-widget/src/components/Frame.vue.mjs +143 -0
  32. package/es/AI-panel-widget/src/components/Header-sfc.css +1 -0
  33. package/es/AI-panel-widget/src/components/Header.vue.d.ts +27 -0
  34. package/es/AI-panel-widget/src/components/Header.vue.mjs +683 -0
  35. package/es/AI-panel-widget/src/components/ResizeHandle-sfc.css +1 -0
  36. package/es/AI-panel-widget/src/components/ResizeHandle.vue.d.ts +22 -0
  37. package/es/AI-panel-widget/src/components/ResizeHandle.vue.mjs +79 -0
  38. package/es/AI-panel-widget/src/components/SelectHint-sfc.css +1 -0
  39. package/es/AI-panel-widget/src/components/SelectHint.vue.d.ts +3 -0
  40. package/es/AI-panel-widget/src/components/SelectHint.vue.mjs +40 -0
  41. package/es/AI-panel-widget/src/components/SelectedBubbles-sfc.css +1 -0
  42. package/es/AI-panel-widget/src/components/SelectedBubbles.vue.d.ts +3 -0
  43. package/es/AI-panel-widget/src/components/SelectedBubbles.vue.mjs +84 -0
  44. package/es/AI-panel-widget/src/components/SelectedNodes-sfc.css +1 -0
  45. package/es/AI-panel-widget/src/components/SelectedNodes.vue.d.ts +3 -0
  46. package/es/AI-panel-widget/src/components/SelectedNodes.vue.mjs +103 -0
  47. package/es/AI-panel-widget/src/components/SessionList-sfc.css +1 -0
  48. package/es/AI-panel-widget/src/components/SessionList.vue.d.ts +13 -0
  49. package/es/AI-panel-widget/src/components/SessionList.vue.mjs +222 -0
  50. package/es/AI-panel-widget/src/components/SplitTrigger-sfc.css +1 -0
  51. package/es/AI-panel-widget/src/components/SplitTrigger.vue.d.ts +18 -0
  52. package/es/AI-panel-widget/src/components/SplitTrigger.vue.mjs +112 -0
  53. package/es/AI-panel-widget/src/components/Trigger-sfc.css +1 -0
  54. package/es/AI-panel-widget/src/components/Trigger.vue.d.ts +28 -0
  55. package/es/AI-panel-widget/src/components/Trigger.vue.mjs +74 -0
  56. package/es/AI-panel-widget/src/context.d.ts +67 -0
  57. package/es/AI-panel-widget/src/context.mjs +16 -0
  58. package/es/AI-panel-widget/src/index-sfc.css +1 -0
  59. package/es/AI-panel-widget/src/index.vue.d.ts +69 -0
  60. package/es/AI-panel-widget/src/index.vue.mjs +782 -0
  61. package/es/AI-panel-widget/src/types.d.ts +2 -0
  62. package/es/AI-panel-widget/src/types.mjs +0 -0
  63. package/es/AI-panel-widget/style/index-pure.d.ts +0 -0
  64. package/es/AI-panel-widget/style/index-pure.mjs +0 -0
  65. package/es/AI-panel-widget/style/index.d.ts +0 -0
  66. package/es/AI-panel-widget/style/index.mjs +0 -0
  67. package/es/AI-panel-widget/style/less-pure.d.ts +0 -0
  68. package/es/AI-panel-widget/style/less-pure.mjs +0 -0
  69. package/es/AI-panel-widget/style/less.d.ts +0 -0
  70. package/es/AI-panel-widget/style/less.mjs +0 -0
  71. package/es/env.d.ts +15 -0
  72. package/es/index.d.ts +7 -0
  73. package/es/index.mjs +22 -0
  74. package/es/setup.d.ts +1 -0
  75. package/es/setup.mjs +4 -0
  76. package/lib/AI-panel-widget/composables/use-inspector.cjs +466 -0
  77. package/lib/AI-panel-widget/composables/use-inspector.d.ts +52 -0
  78. package/lib/AI-panel-widget/composables/use-persist-state.cjs +102 -0
  79. package/lib/AI-panel-widget/composables/use-persist-state.d.ts +32 -0
  80. package/lib/AI-panel-widget/composables/use-selection.cjs +131 -0
  81. package/lib/AI-panel-widget/composables/use-selection.d.ts +19 -0
  82. package/lib/AI-panel-widget/composables/use-session.cjs +66 -0
  83. package/lib/AI-panel-widget/composables/use-session.d.ts +16 -0
  84. package/lib/AI-panel-widget/composables/use-split.cjs +152 -0
  85. package/lib/AI-panel-widget/composables/use-split.d.ts +30 -0
  86. package/lib/AI-panel-widget/composables/use-widget.cjs +97 -0
  87. package/lib/AI-panel-widget/composables/use-widget.d.ts +27 -0
  88. package/lib/AI-panel-widget/index.cjs +39 -0
  89. package/lib/AI-panel-widget/index.d.ts +5 -0
  90. package/lib/AI-panel-widget/src/assets/logo.svg +13 -0
  91. package/lib/AI-panel-widget/src/components/AIPanelLogo-sfc.css +1 -0
  92. package/lib/AI-panel-widget/src/components/AIPanelLogo.vue.cjs +69 -0
  93. package/lib/AI-panel-widget/src/components/AIPanelLogo.vue.d.ts +8 -0
  94. package/lib/AI-panel-widget/src/components/ChatPanel-sfc.css +1 -0
  95. package/lib/AI-panel-widget/src/components/ChatPanel.vue.cjs +337 -0
  96. package/lib/AI-panel-widget/src/components/ChatPanel.vue.d.ts +179 -0
  97. package/lib/AI-panel-widget/src/components/FloatingBubble/FloatingBubble-sfc.css +1 -0
  98. package/lib/AI-panel-widget/src/components/FloatingBubble/FloatingBubble.vue.cjs +330 -0
  99. package/lib/AI-panel-widget/src/components/FloatingBubble/FloatingBubble.vue.d.ts +44 -0
  100. package/lib/AI-panel-widget/src/components/FloatingBubble/index.cjs +35 -0
  101. package/lib/AI-panel-widget/src/components/FloatingBubble/index.d.ts +3 -0
  102. package/lib/AI-panel-widget/src/components/FloatingBubble/types.cjs +15 -0
  103. package/lib/AI-panel-widget/src/components/FloatingBubble/types.d.ts +24 -0
  104. package/lib/AI-panel-widget/src/components/Frame-sfc.css +1 -0
  105. package/lib/AI-panel-widget/src/components/Frame.vue.cjs +162 -0
  106. package/lib/AI-panel-widget/src/components/Frame.vue.d.ts +22 -0
  107. package/lib/AI-panel-widget/src/components/Header-sfc.css +1 -0
  108. package/lib/AI-panel-widget/src/components/Header.vue.cjs +702 -0
  109. package/lib/AI-panel-widget/src/components/Header.vue.d.ts +27 -0
  110. package/lib/AI-panel-widget/src/components/ResizeHandle-sfc.css +1 -0
  111. package/lib/AI-panel-widget/src/components/ResizeHandle.vue.cjs +98 -0
  112. package/lib/AI-panel-widget/src/components/ResizeHandle.vue.d.ts +22 -0
  113. package/lib/AI-panel-widget/src/components/SelectHint-sfc.css +1 -0
  114. package/lib/AI-panel-widget/src/components/SelectHint.vue.cjs +59 -0
  115. package/lib/AI-panel-widget/src/components/SelectHint.vue.d.ts +3 -0
  116. package/lib/AI-panel-widget/src/components/SelectedBubbles-sfc.css +1 -0
  117. package/lib/AI-panel-widget/src/components/SelectedBubbles.vue.cjs +103 -0
  118. package/lib/AI-panel-widget/src/components/SelectedBubbles.vue.d.ts +3 -0
  119. package/lib/AI-panel-widget/src/components/SelectedNodes-sfc.css +1 -0
  120. package/lib/AI-panel-widget/src/components/SelectedNodes.vue.cjs +122 -0
  121. package/lib/AI-panel-widget/src/components/SelectedNodes.vue.d.ts +3 -0
  122. package/lib/AI-panel-widget/src/components/SessionList-sfc.css +1 -0
  123. package/lib/AI-panel-widget/src/components/SessionList.vue.cjs +241 -0
  124. package/lib/AI-panel-widget/src/components/SessionList.vue.d.ts +13 -0
  125. package/lib/AI-panel-widget/src/components/SplitTrigger-sfc.css +1 -0
  126. package/lib/AI-panel-widget/src/components/SplitTrigger.vue.cjs +131 -0
  127. package/lib/AI-panel-widget/src/components/SplitTrigger.vue.d.ts +18 -0
  128. package/lib/AI-panel-widget/src/components/Trigger-sfc.css +1 -0
  129. package/lib/AI-panel-widget/src/components/Trigger.vue.cjs +103 -0
  130. package/lib/AI-panel-widget/src/components/Trigger.vue.d.ts +28 -0
  131. package/lib/AI-panel-widget/src/context.cjs +35 -0
  132. package/lib/AI-panel-widget/src/context.d.ts +67 -0
  133. package/lib/AI-panel-widget/src/index-sfc.css +1 -0
  134. package/lib/AI-panel-widget/src/index.vue.cjs +811 -0
  135. package/lib/AI-panel-widget/src/index.vue.d.ts +69 -0
  136. package/lib/AI-panel-widget/src/types.cjs +15 -0
  137. package/lib/AI-panel-widget/src/types.d.ts +2 -0
  138. package/lib/AI-panel-widget/style/index-pure.cjs +0 -0
  139. package/lib/AI-panel-widget/style/index-pure.d.ts +0 -0
  140. package/lib/AI-panel-widget/style/index.cjs +0 -0
  141. package/lib/AI-panel-widget/style/index.d.ts +0 -0
  142. package/lib/AI-panel-widget/style/less-pure.cjs +0 -0
  143. package/lib/AI-panel-widget/style/less-pure.d.ts +0 -0
  144. package/lib/AI-panel-widget/style/less.cjs +0 -0
  145. package/lib/AI-panel-widget/style/less.d.ts +0 -0
  146. package/lib/env.d.ts +15 -0
  147. package/lib/index.cjs +42 -0
  148. package/lib/index.css +0 -0
  149. package/lib/index.d.ts +7 -0
  150. package/lib/index.less +0 -0
  151. package/lib/setup.cjs +23 -0
  152. package/lib/setup.d.ts +1 -0
  153. package/package.json +42 -0
@@ -0,0 +1,52 @@
1
+ import { type Ref } from "vue";
2
+ import type { AIPanelSelectedElement } from "../src/types";
3
+ interface VueInspector {
4
+ getTargetNode: (e: MouseEvent) => {
5
+ targetNode: Element | null;
6
+ params: {
7
+ file?: string;
8
+ line?: number;
9
+ column?: number;
10
+ } | null;
11
+ };
12
+ handleClick: (e: MouseEvent) => void;
13
+ enable: () => void;
14
+ disable: () => void;
15
+ __aipanel_hooked?: boolean;
16
+ }
17
+ declare global {
18
+ interface Window {
19
+ __VUE_INSPECTOR__?: VueInspector;
20
+ }
21
+ }
22
+ interface UseInspectorOptions {
23
+ selectMode: Ref<boolean>;
24
+ onAddSelectedNode: (element: AIPanelSelectedElement) => void;
25
+ onExitSelectMode: () => void;
26
+ }
27
+ export declare function useInspector(options: UseInspectorOptions): {
28
+ highlightVisible: Ref<boolean, boolean>;
29
+ highlightStyle: Ref<Record<string, string>, Record<string, string>>;
30
+ tooltipVisible: Ref<boolean, boolean>;
31
+ tooltipStyle: Ref<{
32
+ top: string;
33
+ left: string;
34
+ }, {
35
+ top: string;
36
+ left: string;
37
+ } | {
38
+ top: string;
39
+ left: string;
40
+ }>;
41
+ tooltipContent: Ref<{
42
+ description: string;
43
+ fileInfo: string;
44
+ }, {
45
+ description: string;
46
+ fileInfo: string;
47
+ } | {
48
+ description: string;
49
+ fileInfo: string;
50
+ }>;
51
+ };
52
+ export {};
@@ -0,0 +1,437 @@
1
+ import { ref, watch, onMounted, onUnmounted, nextTick } from "vue";
2
+ import { truncate } from "@aipanel/core";
3
+ import getCssSelector from "css-selector-generator";
4
+ const IGNORE_SELECTORS = [
5
+ "#vue-inspector-container",
6
+ ".aipanel-widget",
7
+ ".aipanel-element-highlight",
8
+ ".aipanel-element-tooltip",
9
+ ".aipanel-select-mode-hint",
10
+ ".floating-bubble"
11
+ ];
12
+ const IGNORE_ATTRIBUTE = "data-v-inspector-ignore";
13
+ const KEY_PROPS_DATA = "__v_inspector";
14
+ const KEY_DATA = "data-v-inspector";
15
+ function getDirectText(element) {
16
+ let text = "";
17
+ for (let i = 0; i < element.childNodes.length; i++) {
18
+ const child = element.childNodes[i];
19
+ if (child.nodeType === Node.TEXT_NODE) {
20
+ text += child.textContent || "";
21
+ }
22
+ }
23
+ return text.trim();
24
+ }
25
+ const DYNAMIC_ID_PATTERN = /^(?:el-|:r[0-9]+:|radix-|uid-|ts-|uuid-|id-[a-f0-9]{4,}|.*[0-9]{4,}.*|.*-[a-f0-9]{6,}$)/i;
26
+ const STATE_CLASS_PATTERN = /^(?:hover|active|focus|focus-visible|focus-within|disabled|enabled|checked|selected|open|closed|loading|error|success|warning|hidden|visible|show|hide|current|expanded|collapsed|pressed|dragging|droppable|sortable|placeholder|transition|enter|leave|appear|move)$/i;
27
+ const STATE_CLASS_PREFIX_PATTERN = /^(?:is-|has-|was-|are-|can-|should-|will-|did-|does-|on-|off-|in-|out-|at-|to-|from-)/i;
28
+ function isDynamicId(id) {
29
+ if (!id) return false;
30
+ if (DYNAMIC_ID_PATTERN.test(id)) {
31
+ return true;
32
+ }
33
+ const digitCount = (id.match(/\d/g) || []).length;
34
+ const letterCount = (id.match(/[a-zA-Z]/g) || []).length;
35
+ if (digitCount > letterCount && digitCount >= 3) {
36
+ return true;
37
+ }
38
+ const dashParts = id.split("-");
39
+ if (dashParts.length >= 3) {
40
+ const lastPart = dashParts[dashParts.length - 1];
41
+ if (/^\d+$/.test(lastPart) || /^[a-f0-9]{4,}$/i.test(lastPart)) {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ function isStateClass(className) {
48
+ if (!className) return false;
49
+ if (STATE_CLASS_PATTERN.test(className)) {
50
+ return true;
51
+ }
52
+ if (STATE_CLASS_PREFIX_PATTERN.test(className)) {
53
+ return true;
54
+ }
55
+ if (className.includes("-active") || className.includes("-hover") || className.includes("-focus")) {
56
+ return true;
57
+ }
58
+ if (/^(?:router-link|nuxt-link)/.test(className)) {
59
+ return true;
60
+ }
61
+ return false;
62
+ }
63
+ function getElementDescription(element) {
64
+ return getCssSelector(element, {
65
+ selectors: ["id", "class", "tag", "nthchild"],
66
+ combineWithinSelector: true,
67
+ combineBetweenSelectors: true,
68
+ maxCombinations: 100,
69
+ maxCandidates: 100,
70
+ blacklist: [
71
+ (selectorValue) => {
72
+ const idMatch = selectorValue.match(/^#(.+)$/);
73
+ if (idMatch) {
74
+ return isDynamicId(idMatch[1]);
75
+ }
76
+ const classMatch = selectorValue.match(/^\.([a-zA-Z_-][\w-]*)$/);
77
+ if (classMatch) {
78
+ return isStateClass(classMatch[1]);
79
+ }
80
+ return false;
81
+ }
82
+ ]
83
+ });
84
+ }
85
+ function getFileInfoFromVueInstance(element) {
86
+ const vue3Instance = element.__vueParentComponent;
87
+ if (vue3Instance) {
88
+ let current = vue3Instance;
89
+ while (current) {
90
+ const file = current.type?.__file || current.vnode?.type?.__file;
91
+ if (file) {
92
+ return { file, line: null, column: null };
93
+ }
94
+ current = current.parent;
95
+ }
96
+ }
97
+ const vue2Instance = element.__vue__;
98
+ if (vue2Instance) {
99
+ let current = vue2Instance;
100
+ while (current) {
101
+ const file = current.$options?.__file;
102
+ if (file) {
103
+ return { file, line: null, column: null };
104
+ }
105
+ current = current.$parent;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ function shouldIgnoreElement(el) {
111
+ if (el.hasAttribute(IGNORE_ATTRIBUTE)) return true;
112
+ for (const selector of IGNORE_SELECTORS) {
113
+ if (el.closest(selector)) return true;
114
+ }
115
+ return false;
116
+ }
117
+ function getDataFromElement(el) {
118
+ const vnodeData = el.__vnode?.props?.[KEY_PROPS_DATA];
119
+ if (vnodeData) return vnodeData;
120
+ const ctxVNode = el.__vnode?.ctx?.vnode;
121
+ if (ctxVNode?.el === el) {
122
+ const ctxData = ctxVNode.props?.[KEY_PROPS_DATA];
123
+ if (ctxData) return ctxData;
124
+ }
125
+ const vueInstance = el.__vueParentComponent;
126
+ let currentParent = vueInstance?.parent;
127
+ while (currentParent) {
128
+ if (currentParent.vnode?.el === el) {
129
+ const parentData = currentParent.vnode.props?.[KEY_PROPS_DATA];
130
+ if (parentData) return parentData;
131
+ }
132
+ currentParent = currentParent.parent;
133
+ }
134
+ const attr = el.getAttribute(KEY_DATA);
135
+ return attr ?? void 0;
136
+ }
137
+ function findInspectorFileInfo(element) {
138
+ let current = element;
139
+ while (current) {
140
+ if (shouldIgnoreElement(current)) {
141
+ current = current.parentElement;
142
+ continue;
143
+ }
144
+ const data = getDataFromElement(current);
145
+ if (data) {
146
+ const splitRE = /(.+):([\d]+):([\d]+)$/;
147
+ const match = data.match(splitRE);
148
+ if (match) {
149
+ return {
150
+ file: match[1],
151
+ line: parseInt(match[2], 10),
152
+ column: parseInt(match[3], 10)
153
+ };
154
+ }
155
+ }
156
+ current = current.parentElement;
157
+ }
158
+ return null;
159
+ }
160
+ function mergeFileInfo(inspectorFileInfo, vueFileInfo) {
161
+ if (!inspectorFileInfo?.file && !vueFileInfo?.file) {
162
+ return { file: null, line: null, column: null };
163
+ }
164
+ const isNodeModules = (path) => path.includes("node_modules");
165
+ if (inspectorFileInfo?.file && vueFileInfo?.file) {
166
+ if (!isNodeModules(inspectorFileInfo.file)) {
167
+ return inspectorFileInfo;
168
+ } else if (!isNodeModules(vueFileInfo.file)) {
169
+ return vueFileInfo;
170
+ } else {
171
+ return inspectorFileInfo;
172
+ }
173
+ } else if (inspectorFileInfo?.file) {
174
+ return inspectorFileInfo;
175
+ } else {
176
+ return vueFileInfo;
177
+ }
178
+ }
179
+ function getTargetElement(e) {
180
+ if (!e.target || !(e.target instanceof Element)) return null;
181
+ const el = e.target;
182
+ if (shouldIgnoreElement(el)) return null;
183
+ return el;
184
+ }
185
+ function getFileInfo(e, element) {
186
+ let inspectorFileInfo = null;
187
+ if (element) {
188
+ inspectorFileInfo = findInspectorFileInfo(element);
189
+ }
190
+ const vueFileInfo = element ? getFileInfoFromVueInstance(element) : null;
191
+ return mergeFileInfo(inspectorFileInfo, vueFileInfo);
192
+ }
193
+ function useInspector(options) {
194
+ const highlightVisible = ref(false);
195
+ const highlightStyle = ref({
196
+ top: "0px",
197
+ left: "0px",
198
+ width: "0px",
199
+ height: "0px"
200
+ });
201
+ const tooltipVisible = ref(false);
202
+ const tooltipStyle = ref({ top: "0px", left: "0px" });
203
+ const tooltipContent = ref({ description: "", fileInfo: "" });
204
+ const INSPECTOR_CHECK_INTERVAL = 500;
205
+ let inspectorCheckTimer = null;
206
+ let currentPrimary = "#3b82f6";
207
+ let currentPrimaryBg = "rgba(59, 130, 246, 0.1)";
208
+ function setPointerEventsNone(elements) {
209
+ elements.forEach((el) => {
210
+ if (el) el.style.pointerEvents = "none";
211
+ });
212
+ }
213
+ function setPointerEventsAuto(elements) {
214
+ elements.forEach((el) => {
215
+ if (el) el.style.pointerEvents = "";
216
+ });
217
+ }
218
+ function handleMouseMoveCore(e) {
219
+ if (!options.selectMode.value) return;
220
+ const highlight = document.querySelector(".aipanel-element-highlight");
221
+ const tooltip = document.querySelector(".aipanel-element-tooltip");
222
+ const selectHint = document.querySelector(".aipanel-select-mode-hint");
223
+ const floatingBubble = document.querySelector(".floating-bubble");
224
+ const uiElements = [highlight, tooltip, selectHint, floatingBubble];
225
+ setPointerEventsNone(uiElements);
226
+ const elementToHighlight = getTargetElement(e);
227
+ const fileInfo = getFileInfo(e, elementToHighlight);
228
+ setPointerEventsAuto(uiElements);
229
+ if (elementToHighlight) {
230
+ const widget = document.querySelector(".aipanel-widget");
231
+ if (widget) {
232
+ const style = getComputedStyle(widget);
233
+ currentPrimary = style.getPropertyValue("--ap-primary").trim() || currentPrimary;
234
+ currentPrimaryBg = style.getPropertyValue("--ap-primary-bg").trim() || currentPrimaryBg;
235
+ }
236
+ const description = getElementDescription(elementToHighlight);
237
+ const fileName = fileInfo.file ? fileInfo.file.split("/").pop() : "";
238
+ let lineInfo = "";
239
+ if (fileInfo.line) {
240
+ lineInfo = `:${fileInfo.line}`;
241
+ if (fileInfo.column) {
242
+ lineInfo += `:${fileInfo.column}`;
243
+ }
244
+ }
245
+ const fileInfoText = fileName ? `${fileName}${lineInfo}` : "";
246
+ tooltipContent.value = {
247
+ description,
248
+ fileInfo: fileInfoText
249
+ };
250
+ const rect = elementToHighlight.getBoundingClientRect();
251
+ const newTop = `${rect.top}px`;
252
+ const newLeft = `${rect.left}px`;
253
+ const newWidth = `${rect.width}px`;
254
+ const newHeight = `${rect.height}px`;
255
+ if (highlightStyle.value.top !== newTop || highlightStyle.value.left !== newLeft || highlightStyle.value.width !== newWidth || highlightStyle.value.height !== newHeight) {
256
+ highlightStyle.value = {
257
+ top: newTop,
258
+ left: newLeft,
259
+ width: newWidth,
260
+ height: newHeight,
261
+ border: `2px solid ${currentPrimary}`,
262
+ background: currentPrimaryBg
263
+ };
264
+ }
265
+ highlightVisible.value = true;
266
+ tooltipVisible.value = true;
267
+ void nextTick(() => {
268
+ const tooltipEl = document.querySelector(".aipanel-element-tooltip");
269
+ if (!tooltipEl) return;
270
+ const tooltipWidth = tooltipEl.offsetWidth;
271
+ const tooltipHeight = tooltipEl.offsetHeight;
272
+ if (tooltipWidth === 0 || tooltipHeight === 0) return;
273
+ const margin = 10;
274
+ const gap = 4;
275
+ const viewportWidth = window.innerWidth;
276
+ const viewportHeight = window.innerHeight;
277
+ const clampLeft = (left) => Math.max(margin, Math.min(left, viewportWidth - tooltipWidth - margin));
278
+ const clampTop = (top) => Math.max(margin, Math.min(top, viewportHeight - tooltipHeight - margin));
279
+ const candidateTop = rect.top - tooltipHeight - gap;
280
+ const candidateBottom = rect.bottom + gap;
281
+ const candidateRight = rect.right + gap;
282
+ const candidateLeft = rect.left - tooltipWidth - gap;
283
+ const verticalCenter = rect.top + (rect.height - tooltipHeight) / 2;
284
+ let tooltipTop = 0;
285
+ let tooltipLeft = 0;
286
+ let placed = false;
287
+ if (candidateTop >= margin) {
288
+ tooltipTop = candidateTop;
289
+ tooltipLeft = clampLeft(rect.left);
290
+ placed = true;
291
+ }
292
+ if (!placed && candidateBottom + tooltipHeight <= viewportHeight - margin) {
293
+ tooltipTop = candidateBottom;
294
+ tooltipLeft = clampLeft(rect.left);
295
+ placed = true;
296
+ }
297
+ if (!placed && candidateRight + tooltipWidth <= viewportWidth - margin) {
298
+ tooltipLeft = candidateRight;
299
+ tooltipTop = clampTop(verticalCenter);
300
+ placed = true;
301
+ }
302
+ if (!placed && candidateLeft >= margin) {
303
+ tooltipLeft = candidateLeft;
304
+ tooltipTop = clampTop(verticalCenter);
305
+ placed = true;
306
+ }
307
+ if (!placed) {
308
+ const corners = [
309
+ { top: margin, left: margin },
310
+ { top: margin, left: viewportWidth - tooltipWidth - margin },
311
+ { top: viewportHeight - tooltipHeight - margin, left: margin },
312
+ {
313
+ top: viewportHeight - tooltipHeight - margin,
314
+ left: viewportWidth - tooltipWidth - margin
315
+ }
316
+ ];
317
+ for (const corner of corners) {
318
+ const tooltipRight = corner.left + tooltipWidth;
319
+ const tooltipBottom = corner.top + tooltipHeight;
320
+ const overlaps = tooltipRight <= rect.left || rect.right <= corner.left || tooltipBottom <= rect.top || rect.bottom <= corner.top;
321
+ if (overlaps) {
322
+ tooltipTop = corner.top;
323
+ tooltipLeft = corner.left;
324
+ placed = true;
325
+ break;
326
+ }
327
+ }
328
+ if (!placed) {
329
+ tooltipTop = margin;
330
+ tooltipLeft = margin;
331
+ }
332
+ }
333
+ const newTooltipTop = `${tooltipTop}px`;
334
+ const newTooltipLeft = `${tooltipLeft}px`;
335
+ if (tooltipStyle.value.top !== newTooltipTop || tooltipStyle.value.left !== newTooltipLeft) {
336
+ tooltipStyle.value = {
337
+ top: newTooltipTop,
338
+ left: newTooltipLeft
339
+ };
340
+ }
341
+ });
342
+ } else {
343
+ highlightVisible.value = false;
344
+ tooltipVisible.value = false;
345
+ }
346
+ }
347
+ const handleMouseMove = handleMouseMoveCore;
348
+ function setupInspectorHook() {
349
+ const inspector = window.__VUE_INSPECTOR__;
350
+ if (!inspector || inspector.__aipanel_hooked) return;
351
+ const originalHandleClick = inspector.handleClick.bind(inspector);
352
+ inspector.handleClick = function(e) {
353
+ if (options.selectMode.value) {
354
+ const targetEl = e.target instanceof Element ? e.target : null;
355
+ if (targetEl && targetEl.closest(".aipanel-widget")) {
356
+ return originalHandleClick.call(inspector, e);
357
+ }
358
+ e.preventDefault();
359
+ e.stopPropagation();
360
+ const elementToSelect = getTargetElement(e);
361
+ const fileInfo = getFileInfo(e, elementToSelect);
362
+ if (elementToSelect) {
363
+ const innerText = getDirectText(elementToSelect);
364
+ const description = getElementDescription(elementToSelect);
365
+ const elementInfo = {
366
+ filePath: fileInfo.file,
367
+ line: fileInfo.line,
368
+ column: fileInfo.column,
369
+ innerText: truncate(innerText, 200),
370
+ description
371
+ };
372
+ options.onAddSelectedNode(elementInfo);
373
+ }
374
+ return;
375
+ }
376
+ return originalHandleClick.call(inspector, e);
377
+ };
378
+ inspector.__aipanel_hooked = true;
379
+ }
380
+ function handleKeydown(e) {
381
+ if (e.key === "Escape" && options.selectMode.value) {
382
+ e.preventDefault();
383
+ e.stopPropagation();
384
+ options.onExitSelectMode();
385
+ }
386
+ }
387
+ watch(options.selectMode, (newVal) => {
388
+ const inspector = window.__VUE_INSPECTOR__;
389
+ if (newVal) {
390
+ if (inspector) {
391
+ inspector.enable();
392
+ }
393
+ document.addEventListener("mousemove", handleMouseMove);
394
+ document.addEventListener("keydown", handleKeydown, true);
395
+ } else {
396
+ if (inspector) {
397
+ inspector.disable();
398
+ }
399
+ document.removeEventListener("mousemove", handleMouseMove);
400
+ document.removeEventListener("keydown", handleKeydown, true);
401
+ highlightVisible.value = false;
402
+ tooltipVisible.value = false;
403
+ }
404
+ });
405
+ onMounted(() => {
406
+ if (window.__VUE_INSPECTOR__) {
407
+ setupInspectorHook();
408
+ } else {
409
+ inspectorCheckTimer = window.setInterval(() => {
410
+ if (window.__VUE_INSPECTOR__) {
411
+ setupInspectorHook();
412
+ if (inspectorCheckTimer) {
413
+ window.clearInterval(inspectorCheckTimer);
414
+ inspectorCheckTimer = null;
415
+ }
416
+ }
417
+ }, INSPECTOR_CHECK_INTERVAL);
418
+ }
419
+ });
420
+ onUnmounted(() => {
421
+ if (inspectorCheckTimer) {
422
+ window.clearInterval(inspectorCheckTimer);
423
+ }
424
+ document.removeEventListener("mousemove", handleMouseMove);
425
+ document.removeEventListener("keydown", handleKeydown, true);
426
+ });
427
+ return {
428
+ highlightVisible,
429
+ highlightStyle,
430
+ tooltipVisible,
431
+ tooltipStyle,
432
+ tooltipContent
433
+ };
434
+ }
435
+ export {
436
+ useInspector
437
+ };
@@ -0,0 +1,32 @@
1
+ import { type Ref } from "vue";
2
+ import type { FloatingBubbleOffset } from "../src/components/FloatingBubble/types";
3
+ import type { AIPanelWidgetTheme, DisplayMode } from "../src/types";
4
+ export interface WidgetPersistState {
5
+ open: boolean;
6
+ minimized: boolean;
7
+ promptDockVisible: boolean;
8
+ reviewPanelVisible: boolean;
9
+ bubbleOffset?: FloatingBubbleOffset;
10
+ theme: AIPanelWidgetTheme;
11
+ sessionListCollapsed: boolean;
12
+ splitPanelWidth?: number;
13
+ displayMode?: DisplayMode;
14
+ splitPosition?: "left" | "right";
15
+ }
16
+ export interface UsePersistStateOptions {
17
+ open: Ref<boolean>;
18
+ minimized: Ref<boolean>;
19
+ promptDockVisible: Ref<boolean>;
20
+ reviewPanelVisible: Ref<boolean>;
21
+ bubbleOffset: Ref<FloatingBubbleOffset | undefined>;
22
+ theme: Ref<AIPanelWidgetTheme>;
23
+ sessionListCollapsed: Ref<boolean>;
24
+ splitPanelWidth?: Ref<number>;
25
+ displayMode?: Ref<DisplayMode>;
26
+ splitPosition?: Ref<"left" | "right">;
27
+ onRestore?: (state: Partial<WidgetPersistState>) => void;
28
+ }
29
+ export declare function usePersistState(options: UsePersistStateOptions): {
30
+ restoreState: () => Partial<WidgetPersistState> | null;
31
+ persistState: () => void;
32
+ };
@@ -0,0 +1,83 @@
1
+ import { watch, onMounted } from "vue";
2
+ import { createLogger } from "@aipanel/core";
3
+ const log = createLogger("AIPanelWidget");
4
+ const STORAGE_KEY = "aipanel-widget-state";
5
+ function loadState() {
6
+ if (typeof window === "undefined") return null;
7
+ try {
8
+ const stored = localStorage.getItem(STORAGE_KEY);
9
+ if (stored) {
10
+ return JSON.parse(stored);
11
+ }
12
+ } catch (e) {
13
+ log.error("Failed to load persisted state:", { error: e });
14
+ }
15
+ return null;
16
+ }
17
+ function saveState(state) {
18
+ if (typeof window === "undefined") return;
19
+ try {
20
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
21
+ } catch (e) {
22
+ log.error("Failed to save state:", { error: e });
23
+ }
24
+ }
25
+ function usePersistState(options) {
26
+ const restoreState = () => {
27
+ const saved = loadState();
28
+ if (options.onRestore) {
29
+ options.onRestore(saved || {});
30
+ }
31
+ return saved;
32
+ };
33
+ const getCurrentState = () => ({
34
+ open: options.open.value,
35
+ minimized: options.minimized.value,
36
+ promptDockVisible: options.promptDockVisible.value,
37
+ reviewPanelVisible: options.reviewPanelVisible.value,
38
+ bubbleOffset: options.bubbleOffset.value,
39
+ theme: options.theme.value,
40
+ sessionListCollapsed: options.sessionListCollapsed.value,
41
+ splitPanelWidth: options.splitPanelWidth?.value,
42
+ displayMode: options.displayMode?.value,
43
+ splitPosition: options.splitPosition?.value
44
+ });
45
+ const persistState = () => {
46
+ saveState(getCurrentState());
47
+ };
48
+ const watchers = [
49
+ options.open,
50
+ options.minimized,
51
+ options.promptDockVisible,
52
+ options.reviewPanelVisible,
53
+ options.bubbleOffset,
54
+ options.theme,
55
+ options.sessionListCollapsed
56
+ ];
57
+ if (options.splitPanelWidth) {
58
+ watchers.push(options.splitPanelWidth);
59
+ }
60
+ if (options.displayMode) {
61
+ watchers.push(options.displayMode);
62
+ }
63
+ if (options.splitPosition) {
64
+ watchers.push(options.splitPosition);
65
+ }
66
+ onMounted(() => {
67
+ restoreState();
68
+ watch(
69
+ watchers,
70
+ () => {
71
+ persistState();
72
+ },
73
+ { deep: true }
74
+ );
75
+ });
76
+ return {
77
+ restoreState,
78
+ persistState
79
+ };
80
+ }
81
+ export {
82
+ usePersistState
83
+ };
@@ -0,0 +1,19 @@
1
+ import { type Ref } from "vue";
2
+ import type { AIPanelRemoveSelectedPayload, AIPanelSelectedElement, AIPanelSelectedElementItem } from "../src/types";
3
+ export interface UseSelectionOptions {
4
+ selectMode: Ref<boolean>;
5
+ selectedElements: Ref<AIPanelSelectedElement[]>;
6
+ onToggleSelectMode: (mode: boolean) => void;
7
+ onRemoveSelectedNode: (payload: AIPanelRemoveSelectedPayload) => void;
8
+ onClearSelectedNodes: () => void;
9
+ showConfirmDialog: (message: string) => Promise<boolean>;
10
+ }
11
+ export declare function useSelection(options: UseSelectionOptions): {
12
+ bubbleVisible: import("vue").ComputedRef<boolean>;
13
+ hasSelectedElements: import("vue").ComputedRef<boolean>;
14
+ selectedElementItems: import("vue").ComputedRef<AIPanelSelectedElementItem[]>;
15
+ handleClearSelectedNodes: () => Promise<void>;
16
+ handleClickSelectedNode: (item: AIPanelSelectedElementItem) => void;
17
+ handleRemoveSelectedNode: (item: AIPanelSelectedElementItem, index: number, source: AIPanelRemoveSelectedPayload["source"]) => void;
18
+ handleToggleSelectMode: () => void;
19
+ };