react-performance-advisor 1.0.5 → 1.0.7
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/package.json +1 -1
- package/readme.md +0 -0
- package/dist/index.d.mts +0 -12
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -329
- package/dist/index.mjs +0 -307
package/package.json
CHANGED
package/readme.md
CHANGED
|
Binary file
|
package/dist/index.d.mts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface PerformanceAdvisorProps {
|
|
5
|
-
id: string;
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
}
|
|
8
|
-
declare const PerformanceAdvisor: ({ id, children, }: PerformanceAdvisorProps) => react_jsx_runtime.JSX.Element;
|
|
9
|
-
|
|
10
|
-
declare const PerformanceAdvisorPanel: () => react_jsx_runtime.JSX.Element;
|
|
11
|
-
|
|
12
|
-
export { PerformanceAdvisor, PerformanceAdvisorPanel };
|
package/dist/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface PerformanceAdvisorProps {
|
|
5
|
-
id: string;
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
}
|
|
8
|
-
declare const PerformanceAdvisor: ({ id, children, }: PerformanceAdvisorProps) => react_jsx_runtime.JSX.Element;
|
|
9
|
-
|
|
10
|
-
declare const PerformanceAdvisorPanel: () => react_jsx_runtime.JSX.Element;
|
|
11
|
-
|
|
12
|
-
export { PerformanceAdvisor, PerformanceAdvisorPanel };
|
package/dist/index.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __defProps = Object.defineProperties;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
-
var __spreadValues = (a, b) => {
|
|
12
|
-
for (var prop in b || (b = {}))
|
|
13
|
-
if (__hasOwnProp.call(b, prop))
|
|
14
|
-
__defNormalProp(a, prop, b[prop]);
|
|
15
|
-
if (__getOwnPropSymbols)
|
|
16
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
-
if (__propIsEnum.call(b, prop))
|
|
18
|
-
__defNormalProp(a, prop, b[prop]);
|
|
19
|
-
}
|
|
20
|
-
return a;
|
|
21
|
-
};
|
|
22
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
-
var __export = (target, all) => {
|
|
24
|
-
for (var name in all)
|
|
25
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
-
};
|
|
27
|
-
var __copyProps = (to, from, except, desc) => {
|
|
28
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
-
for (let key of __getOwnPropNames(from))
|
|
30
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
-
}
|
|
33
|
-
return to;
|
|
34
|
-
};
|
|
35
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
-
|
|
37
|
-
// src/index.ts
|
|
38
|
-
var index_exports = {};
|
|
39
|
-
__export(index_exports, {
|
|
40
|
-
PerformanceAdvisor: () => PerformanceAdvisor,
|
|
41
|
-
PerformanceAdvisorPanel: () => PerformanceAdvisorPanel
|
|
42
|
-
});
|
|
43
|
-
module.exports = __toCommonJS(index_exports);
|
|
44
|
-
|
|
45
|
-
// src/PerformanceAdvisor.tsx
|
|
46
|
-
var import_react2 = require("react");
|
|
47
|
-
|
|
48
|
-
// src/useRenderTracker.ts
|
|
49
|
-
var import_react = require("react");
|
|
50
|
-
|
|
51
|
-
// src/store.ts
|
|
52
|
-
var issues = [];
|
|
53
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
54
|
-
var performanceStore = {
|
|
55
|
-
// 1. Add a new issue to the top of the list (keep max 50 to avoid memory leaks)
|
|
56
|
-
addIssue: (issue) => {
|
|
57
|
-
issues = [__spreadProps(__spreadValues({}, issue), { timestamp: Date.now() }), ...issues].slice(0, 50);
|
|
58
|
-
listeners.forEach((listener) => listener());
|
|
59
|
-
},
|
|
60
|
-
// 2. Get all current issues
|
|
61
|
-
getIssues: () => issues,
|
|
62
|
-
// 3. Clear the dashboard
|
|
63
|
-
clear: () => {
|
|
64
|
-
issues = [];
|
|
65
|
-
listeners.forEach((listener) => listener());
|
|
66
|
-
},
|
|
67
|
-
// 4. Let the UI Panel subscribe to changes
|
|
68
|
-
subscribe: (listener) => {
|
|
69
|
-
listeners.add(listener);
|
|
70
|
-
return () => listeners.delete(listener);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// src/useRenderTracker.ts
|
|
75
|
-
function useRenderTracker(componentId, currentProps) {
|
|
76
|
-
const prevProps = (0, import_react.useRef)({});
|
|
77
|
-
const renderCount = (0, import_react.useRef)(0);
|
|
78
|
-
renderCount.current += 1;
|
|
79
|
-
(0, import_react.useEffect)(() => {
|
|
80
|
-
const changedProps = [];
|
|
81
|
-
if (prevProps.current) {
|
|
82
|
-
Object.keys(__spreadValues(__spreadValues({}, prevProps.current), currentProps)).forEach((key) => {
|
|
83
|
-
if (prevProps.current[key] !== currentProps[key]) {
|
|
84
|
-
changedProps.push({
|
|
85
|
-
propName: key,
|
|
86
|
-
prev: prevProps.current[key],
|
|
87
|
-
next: currentProps[key]
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
if (changedProps.length > 0 && renderCount.current > 1) {
|
|
93
|
-
performanceStore.addIssue({
|
|
94
|
-
id: componentId,
|
|
95
|
-
renderCount: renderCount.current,
|
|
96
|
-
changedProps
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
prevProps.current = currentProps;
|
|
100
|
-
});
|
|
101
|
-
return renderCount.current;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/PerformanceAdvisor.tsx
|
|
105
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
106
|
-
var PerformanceAdvisor = ({
|
|
107
|
-
id,
|
|
108
|
-
children
|
|
109
|
-
}) => {
|
|
110
|
-
const childProps = (0, import_react2.isValidElement)(children) ? children.props : {};
|
|
111
|
-
const renderCount = useRenderTracker(id, childProps);
|
|
112
|
-
const handleRender = (profilerId, phase, actualDuration) => {
|
|
113
|
-
if (phase === "update") {
|
|
114
|
-
console.log(
|
|
115
|
-
`[Profiler: ${profilerId}] Render #${renderCount} took ${actualDuration.toFixed(2)}ms`
|
|
116
|
-
);
|
|
117
|
-
const issues2 = performanceStore.getIssues();
|
|
118
|
-
const currentIssue = issues2.find(
|
|
119
|
-
(i) => i.id === profilerId && i.renderCount === renderCount
|
|
120
|
-
);
|
|
121
|
-
if (currentIssue) {
|
|
122
|
-
currentIssue.duration = actualDuration;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const ProfilerComponent = import_react2.Profiler;
|
|
127
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfilerComponent, { id, onRender: handleRender, children });
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// src/PerformanceAdvisorPanel.tsx
|
|
131
|
-
var import_react3 = require("react");
|
|
132
|
-
var import_generative_ai = require("@google/generative-ai");
|
|
133
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
134
|
-
var CodeBlock = ({ code }) => {
|
|
135
|
-
const [copied, setCopied] = (0, import_react3.useState)(false);
|
|
136
|
-
const handleCopy = async () => {
|
|
137
|
-
try {
|
|
138
|
-
await navigator.clipboard.writeText(code);
|
|
139
|
-
setCopied(true);
|
|
140
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error("Failed to copy code", err);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", background: "#1e1e1e", color: "#d4d4d4", padding: "12px", borderRadius: "6px", overflowX: "auto", fontFamily: "monospace", marginTop: "8px", marginBottom: "8px", border: "1px solid #333" }, children: [
|
|
146
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
147
|
-
"button",
|
|
148
|
-
{
|
|
149
|
-
onClick: handleCopy,
|
|
150
|
-
style: { position: "absolute", top: "8px", right: "8px", background: "#333", color: "#fff", border: "1px solid #555", borderRadius: "4px", padding: "4px 8px", cursor: "pointer", fontSize: "11px", transition: "0.2s" },
|
|
151
|
-
children: copied ? "\u2705 Copied!" : "\u{1F4CB} Copy"
|
|
152
|
-
}
|
|
153
|
-
),
|
|
154
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { style: { margin: 0, marginTop: "20px" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: code }) })
|
|
155
|
-
] });
|
|
156
|
-
};
|
|
157
|
-
var PerformanceAdvisorPanel = () => {
|
|
158
|
-
const [issues2, setIssues] = (0, import_react3.useState)([]);
|
|
159
|
-
const [isOpen, setIsOpen] = (0, import_react3.useState)(true);
|
|
160
|
-
const [loadingAi, setLoadingAi] = (0, import_react3.useState)(null);
|
|
161
|
-
const [aiResponses, setAiResponses] = (0, import_react3.useState)({});
|
|
162
|
-
const [apiKey, setApiKey] = (0, import_react3.useState)(() => localStorage.getItem("rpa_gemini_key") || "");
|
|
163
|
-
const [keyInput, setKeyInput] = (0, import_react3.useState)("");
|
|
164
|
-
const [keyError, setKeyError] = (0, import_react3.useState)(false);
|
|
165
|
-
(0, import_react3.useEffect)(() => {
|
|
166
|
-
const unsubscribe = performanceStore.subscribe(() => {
|
|
167
|
-
setIssues([...performanceStore.getIssues()]);
|
|
168
|
-
});
|
|
169
|
-
return () => {
|
|
170
|
-
unsubscribe();
|
|
171
|
-
};
|
|
172
|
-
}, []);
|
|
173
|
-
const groupedIssues = (0, import_react3.useMemo)(() => {
|
|
174
|
-
const groups = {};
|
|
175
|
-
issues2.forEach((issue) => {
|
|
176
|
-
const signature = issue.changedProps.map((p) => p.propName).sort().join(",");
|
|
177
|
-
if (!groups[signature]) {
|
|
178
|
-
groups[signature] = __spreadProps(__spreadValues({}, issue), {
|
|
179
|
-
affectedCount: 1,
|
|
180
|
-
totalDuration: issue.duration || 0,
|
|
181
|
-
baseId: issue.id
|
|
182
|
-
});
|
|
183
|
-
} else {
|
|
184
|
-
groups[signature].affectedCount += 1;
|
|
185
|
-
groups[signature].totalDuration += issue.duration || 0;
|
|
186
|
-
groups[signature].renderCount = Math.max(groups[signature].renderCount, issue.renderCount);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
return Object.values(groups).map((group) => {
|
|
190
|
-
if (group.affectedCount > 1) {
|
|
191
|
-
group.id = `${group.baseId} (+ ${group.affectedCount - 1} identical components)`;
|
|
192
|
-
group.duration = group.totalDuration;
|
|
193
|
-
}
|
|
194
|
-
return group;
|
|
195
|
-
});
|
|
196
|
-
}, [issues2]);
|
|
197
|
-
const askGemini = async (issue, index) => {
|
|
198
|
-
if (!apiKey) {
|
|
199
|
-
setKeyError(true);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
setKeyError(false);
|
|
203
|
-
const issueKey = `${issue.id}-${index}`;
|
|
204
|
-
setLoadingAi(issueKey);
|
|
205
|
-
try {
|
|
206
|
-
const genAI = new import_generative_ai.GoogleGenerativeAI(apiKey);
|
|
207
|
-
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
208
|
-
const changedPropsList = issue.changedProps.map((p) => p.propName).join(", ");
|
|
209
|
-
const prompt = `
|
|
210
|
-
You are an expert React performance consultant.
|
|
211
|
-
A React component named '<${issue.id}>' just re-rendered ${issue.renderCount} times.
|
|
212
|
-
${issue.duration ? `CRITICAL: The collective render time took a massive ${issue.duration.toFixed(2)} milliseconds, which causes UI lag.` : ""}
|
|
213
|
-
The props that changed are: ${changedPropsList}.
|
|
214
|
-
|
|
215
|
-
If the render duration is high (>16ms), explain why heavy synchronous tasks block the main thread and suggest moving the calculation to a useMemo, a Web Worker, or useEffect. Give a brief explanation and a short code fix.
|
|
216
|
-
`;
|
|
217
|
-
const result = await model.generateContent(prompt);
|
|
218
|
-
setAiResponses((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
219
|
-
[issueKey]: result.response.text()
|
|
220
|
-
}));
|
|
221
|
-
} catch (error) {
|
|
222
|
-
console.error("AI Error:", error);
|
|
223
|
-
if (error instanceof Error && error.message.includes("API key")) {
|
|
224
|
-
localStorage.removeItem("rpa_gemini_key");
|
|
225
|
-
setApiKey("");
|
|
226
|
-
}
|
|
227
|
-
setAiResponses((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
228
|
-
[issueKey]: "Failed to connect to AI. Check console, or your API key might be invalid."
|
|
229
|
-
}));
|
|
230
|
-
} finally {
|
|
231
|
-
setLoadingAi(null);
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
const renderFormattedText = (text) => {
|
|
235
|
-
return text.split("```").map((part, index) => {
|
|
236
|
-
if (index % 2 === 1) {
|
|
237
|
-
const cleanCode = part.replace(/^[\w-]+\n/, "");
|
|
238
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CodeBlock, { code: cleanCode }, index);
|
|
239
|
-
}
|
|
240
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: part }, index);
|
|
241
|
-
});
|
|
242
|
-
};
|
|
243
|
-
const handleSaveKey = () => {
|
|
244
|
-
if (keyInput.trim()) {
|
|
245
|
-
localStorage.setItem("rpa_gemini_key", keyInput.trim());
|
|
246
|
-
setApiKey(keyInput.trim());
|
|
247
|
-
setKeyError(false);
|
|
248
|
-
setKeyInput("");
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
if (!isOpen) {
|
|
252
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
253
|
-
"button",
|
|
254
|
-
{
|
|
255
|
-
onClick: () => setIsOpen(true),
|
|
256
|
-
style: { position: "fixed", bottom: 20, right: 20, background: "#333", color: "#fff", padding: "10px", borderRadius: "8px", zIndex: 9999 },
|
|
257
|
-
children: [
|
|
258
|
-
"\u{1F50D} Open Advisor (",
|
|
259
|
-
groupedIssues.length,
|
|
260
|
-
")"
|
|
261
|
-
]
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "fixed", bottom: 20, right: 20, width: "450px", background: "#fff", border: "1px solid #ccc", borderRadius: "8px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", zIndex: 9999, fontFamily: "sans-serif", color: "#333", display: "flex", flexDirection: "column" }, children: [
|
|
266
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { background: "#333", color: "#fff", padding: "10px", borderTopLeftRadius: "8px", borderTopRightRadius: "8px", display: "flex", justifyContent: "space-between" }, children: [
|
|
267
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "\u{1F9E0} Performance Advisor" }),
|
|
268
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setIsOpen(false), style: { background: "transparent", border: "none", color: "#fff", cursor: "pointer" }, children: "\u2716" })
|
|
269
|
-
] }),
|
|
270
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { maxHeight: "400px", overflowY: "auto", padding: "10px", flexGrow: 1 }, children: groupedIssues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#666", textAlign: "center" }, children: "No issues detected yet. Click around your app!" }) : groupedIssues.map((issue, index) => {
|
|
271
|
-
const issueKey = `${issue.id}-${index}`;
|
|
272
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { borderBottom: "1px solid #eee", paddingBottom: "10px", marginBottom: "10px" }, children: [
|
|
273
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "#d9534f", fontWeight: "bold", fontSize: "14px" }, children: [
|
|
274
|
-
"\u26A0\uFE0F ",
|
|
275
|
-
issue.id
|
|
276
|
-
] }),
|
|
277
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: "13px", marginTop: "5px", color: "#333" }, children: [
|
|
278
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Changed Props:" }),
|
|
279
|
-
" ",
|
|
280
|
-
issue.changedProps.map((p) => p.propName).join(", "),
|
|
281
|
-
issue.duration && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { color: "#e65100", fontWeight: "bold", marginLeft: "8px" }, children: [
|
|
282
|
-
"\u23F1\uFE0F ",
|
|
283
|
-
issue.duration.toFixed(1),
|
|
284
|
-
"ms Lag"
|
|
285
|
-
] })
|
|
286
|
-
] }),
|
|
287
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
|
|
288
|
-
!aiResponses[issueKey] && loadingAi !== issueKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => askGemini(issue, index), style: { background: "#e1f5fe", border: "1px solid #0288d1", color: "#0288d1", padding: "6px 12px", borderRadius: "4px", cursor: "pointer", fontSize: "12px", fontWeight: "bold" }, children: "\u2728 Ask AI How to Fix This" }),
|
|
289
|
-
loadingAi === issueKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "12px", color: "#666" }, children: "\u{1F9E0} AI is diagnosing..." }),
|
|
290
|
-
aiResponses[issueKey] && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { background: "#f0f4f8", color: "#1e293b", padding: "12px", borderRadius: "6px", marginTop: "8px", fontSize: "13px", lineHeight: "1.5", borderLeft: "4px solid #0288d1", whiteSpace: "pre-wrap", boxShadow: "inset 0 1px 3px rgba(0,0,0,0.05)" }, children: [
|
|
291
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { style: { color: "#0288d1", display: "block", marginBottom: "4px" }, children: "\u2728 AI Suggestion:" }),
|
|
292
|
-
renderFormattedText(aiResponses[issueKey])
|
|
293
|
-
] })
|
|
294
|
-
] })
|
|
295
|
-
] }, index);
|
|
296
|
-
}) }),
|
|
297
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "10px", borderTop: "1px solid #eee", background: "#fafafa", borderBottomLeftRadius: "8px", borderBottomRightRadius: "8px" }, children: !apiKey ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
298
|
-
keyError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "red", fontSize: "12px", margin: "0 0 5px 0" }, children: "\u26A0\uFE0F Please save your API key first!" }),
|
|
299
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
300
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
301
|
-
"input",
|
|
302
|
-
{
|
|
303
|
-
type: "password",
|
|
304
|
-
placeholder: "Enter Gemini API Key...",
|
|
305
|
-
value: keyInput,
|
|
306
|
-
onChange: (e) => setKeyInput(e.target.value),
|
|
307
|
-
style: { flexGrow: 1, padding: "6px", borderRadius: "4px", border: "1px solid #ccc" }
|
|
308
|
-
}
|
|
309
|
-
),
|
|
310
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: handleSaveKey, style: { background: "#0288d1", color: "#fff", border: "none", padding: "6px 12px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold" }, children: "Save" })
|
|
311
|
-
] }),
|
|
312
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "11px", color: "#666", margin: "5px 0 0 0" }, children: "Key is saved securely in your browser's Local Storage." })
|
|
313
|
-
] }) : (
|
|
314
|
-
/* If key exists, show normal controls */
|
|
315
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
316
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => performanceStore.clear(), style: { padding: "5px 10px", cursor: "pointer", color: "#333", background: "#e0e0e0", border: "1px solid #ccc", borderRadius: "4px" }, children: "Clear Logs" }),
|
|
317
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => {
|
|
318
|
-
localStorage.removeItem("rpa_gemini_key");
|
|
319
|
-
setApiKey("");
|
|
320
|
-
}, style: { fontSize: "11px", background: "none", border: "none", color: "#888", cursor: "pointer", textDecoration: "underline" }, children: "Remove API Key" })
|
|
321
|
-
] })
|
|
322
|
-
) })
|
|
323
|
-
] });
|
|
324
|
-
};
|
|
325
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
326
|
-
0 && (module.exports = {
|
|
327
|
-
PerformanceAdvisor,
|
|
328
|
-
PerformanceAdvisorPanel
|
|
329
|
-
});
|
package/dist/index.mjs
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defProps = Object.defineProperties;
|
|
3
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
-
var __spreadValues = (a, b) => {
|
|
9
|
-
for (var prop in b || (b = {}))
|
|
10
|
-
if (__hasOwnProp.call(b, prop))
|
|
11
|
-
__defNormalProp(a, prop, b[prop]);
|
|
12
|
-
if (__getOwnPropSymbols)
|
|
13
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
-
if (__propIsEnum.call(b, prop))
|
|
15
|
-
__defNormalProp(a, prop, b[prop]);
|
|
16
|
-
}
|
|
17
|
-
return a;
|
|
18
|
-
};
|
|
19
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
-
|
|
21
|
-
// src/PerformanceAdvisor.tsx
|
|
22
|
-
import {
|
|
23
|
-
Profiler,
|
|
24
|
-
isValidElement
|
|
25
|
-
} from "react";
|
|
26
|
-
|
|
27
|
-
// src/useRenderTracker.ts
|
|
28
|
-
import { useRef, useEffect } from "react";
|
|
29
|
-
|
|
30
|
-
// src/store.ts
|
|
31
|
-
var issues = [];
|
|
32
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
33
|
-
var performanceStore = {
|
|
34
|
-
// 1. Add a new issue to the top of the list (keep max 50 to avoid memory leaks)
|
|
35
|
-
addIssue: (issue) => {
|
|
36
|
-
issues = [__spreadProps(__spreadValues({}, issue), { timestamp: Date.now() }), ...issues].slice(0, 50);
|
|
37
|
-
listeners.forEach((listener) => listener());
|
|
38
|
-
},
|
|
39
|
-
// 2. Get all current issues
|
|
40
|
-
getIssues: () => issues,
|
|
41
|
-
// 3. Clear the dashboard
|
|
42
|
-
clear: () => {
|
|
43
|
-
issues = [];
|
|
44
|
-
listeners.forEach((listener) => listener());
|
|
45
|
-
},
|
|
46
|
-
// 4. Let the UI Panel subscribe to changes
|
|
47
|
-
subscribe: (listener) => {
|
|
48
|
-
listeners.add(listener);
|
|
49
|
-
return () => listeners.delete(listener);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// src/useRenderTracker.ts
|
|
54
|
-
function useRenderTracker(componentId, currentProps) {
|
|
55
|
-
const prevProps = useRef({});
|
|
56
|
-
const renderCount = useRef(0);
|
|
57
|
-
renderCount.current += 1;
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const changedProps = [];
|
|
60
|
-
if (prevProps.current) {
|
|
61
|
-
Object.keys(__spreadValues(__spreadValues({}, prevProps.current), currentProps)).forEach((key) => {
|
|
62
|
-
if (prevProps.current[key] !== currentProps[key]) {
|
|
63
|
-
changedProps.push({
|
|
64
|
-
propName: key,
|
|
65
|
-
prev: prevProps.current[key],
|
|
66
|
-
next: currentProps[key]
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (changedProps.length > 0 && renderCount.current > 1) {
|
|
72
|
-
performanceStore.addIssue({
|
|
73
|
-
id: componentId,
|
|
74
|
-
renderCount: renderCount.current,
|
|
75
|
-
changedProps
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
prevProps.current = currentProps;
|
|
79
|
-
});
|
|
80
|
-
return renderCount.current;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/PerformanceAdvisor.tsx
|
|
84
|
-
import { jsx } from "react/jsx-runtime";
|
|
85
|
-
var PerformanceAdvisor = ({
|
|
86
|
-
id,
|
|
87
|
-
children
|
|
88
|
-
}) => {
|
|
89
|
-
const childProps = isValidElement(children) ? children.props : {};
|
|
90
|
-
const renderCount = useRenderTracker(id, childProps);
|
|
91
|
-
const handleRender = (profilerId, phase, actualDuration) => {
|
|
92
|
-
if (phase === "update") {
|
|
93
|
-
console.log(
|
|
94
|
-
`[Profiler: ${profilerId}] Render #${renderCount} took ${actualDuration.toFixed(2)}ms`
|
|
95
|
-
);
|
|
96
|
-
const issues2 = performanceStore.getIssues();
|
|
97
|
-
const currentIssue = issues2.find(
|
|
98
|
-
(i) => i.id === profilerId && i.renderCount === renderCount
|
|
99
|
-
);
|
|
100
|
-
if (currentIssue) {
|
|
101
|
-
currentIssue.duration = actualDuration;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const ProfilerComponent = Profiler;
|
|
106
|
-
return /* @__PURE__ */ jsx(ProfilerComponent, { id, onRender: handleRender, children });
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// src/PerformanceAdvisorPanel.tsx
|
|
110
|
-
import { useEffect as useEffect2, useState, useMemo } from "react";
|
|
111
|
-
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
112
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
113
|
-
var CodeBlock = ({ code }) => {
|
|
114
|
-
const [copied, setCopied] = useState(false);
|
|
115
|
-
const handleCopy = async () => {
|
|
116
|
-
try {
|
|
117
|
-
await navigator.clipboard.writeText(code);
|
|
118
|
-
setCopied(true);
|
|
119
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
console.error("Failed to copy code", err);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
return /* @__PURE__ */ jsxs("div", { style: { position: "relative", background: "#1e1e1e", color: "#d4d4d4", padding: "12px", borderRadius: "6px", overflowX: "auto", fontFamily: "monospace", marginTop: "8px", marginBottom: "8px", border: "1px solid #333" }, children: [
|
|
125
|
-
/* @__PURE__ */ jsx2(
|
|
126
|
-
"button",
|
|
127
|
-
{
|
|
128
|
-
onClick: handleCopy,
|
|
129
|
-
style: { position: "absolute", top: "8px", right: "8px", background: "#333", color: "#fff", border: "1px solid #555", borderRadius: "4px", padding: "4px 8px", cursor: "pointer", fontSize: "11px", transition: "0.2s" },
|
|
130
|
-
children: copied ? "\u2705 Copied!" : "\u{1F4CB} Copy"
|
|
131
|
-
}
|
|
132
|
-
),
|
|
133
|
-
/* @__PURE__ */ jsx2("pre", { style: { margin: 0, marginTop: "20px" }, children: /* @__PURE__ */ jsx2("code", { children: code }) })
|
|
134
|
-
] });
|
|
135
|
-
};
|
|
136
|
-
var PerformanceAdvisorPanel = () => {
|
|
137
|
-
const [issues2, setIssues] = useState([]);
|
|
138
|
-
const [isOpen, setIsOpen] = useState(true);
|
|
139
|
-
const [loadingAi, setLoadingAi] = useState(null);
|
|
140
|
-
const [aiResponses, setAiResponses] = useState({});
|
|
141
|
-
const [apiKey, setApiKey] = useState(() => localStorage.getItem("rpa_gemini_key") || "");
|
|
142
|
-
const [keyInput, setKeyInput] = useState("");
|
|
143
|
-
const [keyError, setKeyError] = useState(false);
|
|
144
|
-
useEffect2(() => {
|
|
145
|
-
const unsubscribe = performanceStore.subscribe(() => {
|
|
146
|
-
setIssues([...performanceStore.getIssues()]);
|
|
147
|
-
});
|
|
148
|
-
return () => {
|
|
149
|
-
unsubscribe();
|
|
150
|
-
};
|
|
151
|
-
}, []);
|
|
152
|
-
const groupedIssues = useMemo(() => {
|
|
153
|
-
const groups = {};
|
|
154
|
-
issues2.forEach((issue) => {
|
|
155
|
-
const signature = issue.changedProps.map((p) => p.propName).sort().join(",");
|
|
156
|
-
if (!groups[signature]) {
|
|
157
|
-
groups[signature] = __spreadProps(__spreadValues({}, issue), {
|
|
158
|
-
affectedCount: 1,
|
|
159
|
-
totalDuration: issue.duration || 0,
|
|
160
|
-
baseId: issue.id
|
|
161
|
-
});
|
|
162
|
-
} else {
|
|
163
|
-
groups[signature].affectedCount += 1;
|
|
164
|
-
groups[signature].totalDuration += issue.duration || 0;
|
|
165
|
-
groups[signature].renderCount = Math.max(groups[signature].renderCount, issue.renderCount);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
return Object.values(groups).map((group) => {
|
|
169
|
-
if (group.affectedCount > 1) {
|
|
170
|
-
group.id = `${group.baseId} (+ ${group.affectedCount - 1} identical components)`;
|
|
171
|
-
group.duration = group.totalDuration;
|
|
172
|
-
}
|
|
173
|
-
return group;
|
|
174
|
-
});
|
|
175
|
-
}, [issues2]);
|
|
176
|
-
const askGemini = async (issue, index) => {
|
|
177
|
-
if (!apiKey) {
|
|
178
|
-
setKeyError(true);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
setKeyError(false);
|
|
182
|
-
const issueKey = `${issue.id}-${index}`;
|
|
183
|
-
setLoadingAi(issueKey);
|
|
184
|
-
try {
|
|
185
|
-
const genAI = new GoogleGenerativeAI(apiKey);
|
|
186
|
-
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
187
|
-
const changedPropsList = issue.changedProps.map((p) => p.propName).join(", ");
|
|
188
|
-
const prompt = `
|
|
189
|
-
You are an expert React performance consultant.
|
|
190
|
-
A React component named '<${issue.id}>' just re-rendered ${issue.renderCount} times.
|
|
191
|
-
${issue.duration ? `CRITICAL: The collective render time took a massive ${issue.duration.toFixed(2)} milliseconds, which causes UI lag.` : ""}
|
|
192
|
-
The props that changed are: ${changedPropsList}.
|
|
193
|
-
|
|
194
|
-
If the render duration is high (>16ms), explain why heavy synchronous tasks block the main thread and suggest moving the calculation to a useMemo, a Web Worker, or useEffect. Give a brief explanation and a short code fix.
|
|
195
|
-
`;
|
|
196
|
-
const result = await model.generateContent(prompt);
|
|
197
|
-
setAiResponses((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
198
|
-
[issueKey]: result.response.text()
|
|
199
|
-
}));
|
|
200
|
-
} catch (error) {
|
|
201
|
-
console.error("AI Error:", error);
|
|
202
|
-
if (error instanceof Error && error.message.includes("API key")) {
|
|
203
|
-
localStorage.removeItem("rpa_gemini_key");
|
|
204
|
-
setApiKey("");
|
|
205
|
-
}
|
|
206
|
-
setAiResponses((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
207
|
-
[issueKey]: "Failed to connect to AI. Check console, or your API key might be invalid."
|
|
208
|
-
}));
|
|
209
|
-
} finally {
|
|
210
|
-
setLoadingAi(null);
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
const renderFormattedText = (text) => {
|
|
214
|
-
return text.split("```").map((part, index) => {
|
|
215
|
-
if (index % 2 === 1) {
|
|
216
|
-
const cleanCode = part.replace(/^[\w-]+\n/, "");
|
|
217
|
-
return /* @__PURE__ */ jsx2(CodeBlock, { code: cleanCode }, index);
|
|
218
|
-
}
|
|
219
|
-
return /* @__PURE__ */ jsx2("span", { children: part }, index);
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
const handleSaveKey = () => {
|
|
223
|
-
if (keyInput.trim()) {
|
|
224
|
-
localStorage.setItem("rpa_gemini_key", keyInput.trim());
|
|
225
|
-
setApiKey(keyInput.trim());
|
|
226
|
-
setKeyError(false);
|
|
227
|
-
setKeyInput("");
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
if (!isOpen) {
|
|
231
|
-
return /* @__PURE__ */ jsxs(
|
|
232
|
-
"button",
|
|
233
|
-
{
|
|
234
|
-
onClick: () => setIsOpen(true),
|
|
235
|
-
style: { position: "fixed", bottom: 20, right: 20, background: "#333", color: "#fff", padding: "10px", borderRadius: "8px", zIndex: 9999 },
|
|
236
|
-
children: [
|
|
237
|
-
"\u{1F50D} Open Advisor (",
|
|
238
|
-
groupedIssues.length,
|
|
239
|
-
")"
|
|
240
|
-
]
|
|
241
|
-
}
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
return /* @__PURE__ */ jsxs("div", { style: { position: "fixed", bottom: 20, right: 20, width: "450px", background: "#fff", border: "1px solid #ccc", borderRadius: "8px", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", zIndex: 9999, fontFamily: "sans-serif", color: "#333", display: "flex", flexDirection: "column" }, children: [
|
|
245
|
-
/* @__PURE__ */ jsxs("div", { style: { background: "#333", color: "#fff", padding: "10px", borderTopLeftRadius: "8px", borderTopRightRadius: "8px", display: "flex", justifyContent: "space-between" }, children: [
|
|
246
|
-
/* @__PURE__ */ jsx2("strong", { children: "\u{1F9E0} Performance Advisor" }),
|
|
247
|
-
/* @__PURE__ */ jsx2("button", { onClick: () => setIsOpen(false), style: { background: "transparent", border: "none", color: "#fff", cursor: "pointer" }, children: "\u2716" })
|
|
248
|
-
] }),
|
|
249
|
-
/* @__PURE__ */ jsx2("div", { style: { maxHeight: "400px", overflowY: "auto", padding: "10px", flexGrow: 1 }, children: groupedIssues.length === 0 ? /* @__PURE__ */ jsx2("p", { style: { color: "#666", textAlign: "center" }, children: "No issues detected yet. Click around your app!" }) : groupedIssues.map((issue, index) => {
|
|
250
|
-
const issueKey = `${issue.id}-${index}`;
|
|
251
|
-
return /* @__PURE__ */ jsxs("div", { style: { borderBottom: "1px solid #eee", paddingBottom: "10px", marginBottom: "10px" }, children: [
|
|
252
|
-
/* @__PURE__ */ jsxs("div", { style: { color: "#d9534f", fontWeight: "bold", fontSize: "14px" }, children: [
|
|
253
|
-
"\u26A0\uFE0F ",
|
|
254
|
-
issue.id
|
|
255
|
-
] }),
|
|
256
|
-
/* @__PURE__ */ jsxs("div", { style: { fontSize: "13px", marginTop: "5px", color: "#333" }, children: [
|
|
257
|
-
/* @__PURE__ */ jsx2("strong", { children: "Changed Props:" }),
|
|
258
|
-
" ",
|
|
259
|
-
issue.changedProps.map((p) => p.propName).join(", "),
|
|
260
|
-
issue.duration && /* @__PURE__ */ jsxs("span", { style: { color: "#e65100", fontWeight: "bold", marginLeft: "8px" }, children: [
|
|
261
|
-
"\u23F1\uFE0F ",
|
|
262
|
-
issue.duration.toFixed(1),
|
|
263
|
-
"ms Lag"
|
|
264
|
-
] })
|
|
265
|
-
] }),
|
|
266
|
-
/* @__PURE__ */ jsxs("div", { style: { marginTop: "10px" }, children: [
|
|
267
|
-
!aiResponses[issueKey] && loadingAi !== issueKey && /* @__PURE__ */ jsx2("button", { onClick: () => askGemini(issue, index), style: { background: "#e1f5fe", border: "1px solid #0288d1", color: "#0288d1", padding: "6px 12px", borderRadius: "4px", cursor: "pointer", fontSize: "12px", fontWeight: "bold" }, children: "\u2728 Ask AI How to Fix This" }),
|
|
268
|
-
loadingAi === issueKey && /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#666" }, children: "\u{1F9E0} AI is diagnosing..." }),
|
|
269
|
-
aiResponses[issueKey] && /* @__PURE__ */ jsxs("div", { style: { background: "#f0f4f8", color: "#1e293b", padding: "12px", borderRadius: "6px", marginTop: "8px", fontSize: "13px", lineHeight: "1.5", borderLeft: "4px solid #0288d1", whiteSpace: "pre-wrap", boxShadow: "inset 0 1px 3px rgba(0,0,0,0.05)" }, children: [
|
|
270
|
-
/* @__PURE__ */ jsx2("strong", { style: { color: "#0288d1", display: "block", marginBottom: "4px" }, children: "\u2728 AI Suggestion:" }),
|
|
271
|
-
renderFormattedText(aiResponses[issueKey])
|
|
272
|
-
] })
|
|
273
|
-
] })
|
|
274
|
-
] }, index);
|
|
275
|
-
}) }),
|
|
276
|
-
/* @__PURE__ */ jsx2("div", { style: { padding: "10px", borderTop: "1px solid #eee", background: "#fafafa", borderBottomLeftRadius: "8px", borderBottomRightRadius: "8px" }, children: !apiKey ? /* @__PURE__ */ jsxs("div", { children: [
|
|
277
|
-
keyError && /* @__PURE__ */ jsx2("p", { style: { color: "red", fontSize: "12px", margin: "0 0 5px 0" }, children: "\u26A0\uFE0F Please save your API key first!" }),
|
|
278
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
279
|
-
/* @__PURE__ */ jsx2(
|
|
280
|
-
"input",
|
|
281
|
-
{
|
|
282
|
-
type: "password",
|
|
283
|
-
placeholder: "Enter Gemini API Key...",
|
|
284
|
-
value: keyInput,
|
|
285
|
-
onChange: (e) => setKeyInput(e.target.value),
|
|
286
|
-
style: { flexGrow: 1, padding: "6px", borderRadius: "4px", border: "1px solid #ccc" }
|
|
287
|
-
}
|
|
288
|
-
),
|
|
289
|
-
/* @__PURE__ */ jsx2("button", { onClick: handleSaveKey, style: { background: "#0288d1", color: "#fff", border: "none", padding: "6px 12px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold" }, children: "Save" })
|
|
290
|
-
] }),
|
|
291
|
-
/* @__PURE__ */ jsx2("p", { style: { fontSize: "11px", color: "#666", margin: "5px 0 0 0" }, children: "Key is saved securely in your browser's Local Storage." })
|
|
292
|
-
] }) : (
|
|
293
|
-
/* If key exists, show normal controls */
|
|
294
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
295
|
-
/* @__PURE__ */ jsx2("button", { onClick: () => performanceStore.clear(), style: { padding: "5px 10px", cursor: "pointer", color: "#333", background: "#e0e0e0", border: "1px solid #ccc", borderRadius: "4px" }, children: "Clear Logs" }),
|
|
296
|
-
/* @__PURE__ */ jsx2("button", { onClick: () => {
|
|
297
|
-
localStorage.removeItem("rpa_gemini_key");
|
|
298
|
-
setApiKey("");
|
|
299
|
-
}, style: { fontSize: "11px", background: "none", border: "none", color: "#888", cursor: "pointer", textDecoration: "underline" }, children: "Remove API Key" })
|
|
300
|
-
] })
|
|
301
|
-
) })
|
|
302
|
-
] });
|
|
303
|
-
};
|
|
304
|
-
export {
|
|
305
|
-
PerformanceAdvisor,
|
|
306
|
-
PerformanceAdvisorPanel
|
|
307
|
-
};
|