@kedataindo/docflow-core 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +190 -1
- package/dist/index.d.cts +73 -1
- package/dist/index.d.ts +73 -1
- package/dist/index.js +188 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
|
|
38
38
|
PAGE_SIZES: () => PAGE_SIZES,
|
|
39
39
|
PaginationPlus: () => PaginationPlus,
|
|
40
|
+
PerformanceMonitor: () => PerformanceMonitor,
|
|
40
41
|
SearchAndReplaceExtension: () => SearchAndReplaceExtension,
|
|
41
42
|
SubdocumentProvider: () => SubdocumentProvider,
|
|
42
43
|
buildAIPrompt: () => buildAIPrompt,
|
|
@@ -46,6 +47,7 @@ __export(index_exports, {
|
|
|
46
47
|
createActionMap: () => createActionMap,
|
|
47
48
|
createCollaboration: () => createCollaboration,
|
|
48
49
|
createEditor: () => createEditor,
|
|
50
|
+
createPerformanceMonitor: () => createPerformanceMonitor,
|
|
49
51
|
definePlugin: () => definePlugin,
|
|
50
52
|
findMatches: () => findMatches,
|
|
51
53
|
getSearchState: () => getSearchState,
|
|
@@ -1262,6 +1264,188 @@ var PAGE_SIZES = {
|
|
|
1262
1264
|
TABLOID: TABLOID_PAGE_SIZE
|
|
1263
1265
|
};
|
|
1264
1266
|
|
|
1267
|
+
// src/PerformanceMonitor.ts
|
|
1268
|
+
var TARGET_FRAME_MS = 1e3 / 60;
|
|
1269
|
+
var formatMb = (bytes) => `${Math.round(bytes / 1024 / 1024)}MB`;
|
|
1270
|
+
var PerformanceMonitor = class {
|
|
1271
|
+
container;
|
|
1272
|
+
interval;
|
|
1273
|
+
metrics;
|
|
1274
|
+
root = null;
|
|
1275
|
+
cpuBar = null;
|
|
1276
|
+
cpuValue = null;
|
|
1277
|
+
ramBar = null;
|
|
1278
|
+
ramValue = null;
|
|
1279
|
+
rafId = 0;
|
|
1280
|
+
lastFrame = 0;
|
|
1281
|
+
busyMs = 0;
|
|
1282
|
+
windowStart = 0;
|
|
1283
|
+
timer = null;
|
|
1284
|
+
longTaskObserver = null;
|
|
1285
|
+
destroyed = false;
|
|
1286
|
+
constructor(options = {}) {
|
|
1287
|
+
this.container = options.container ?? (typeof document !== "undefined" ? document.body : void 0);
|
|
1288
|
+
this.interval = options.interval ?? 1e3;
|
|
1289
|
+
this.metrics = new Set(options.metrics ?? ["cpu", "ram"]);
|
|
1290
|
+
}
|
|
1291
|
+
/** Append the widget to the container and start sampling. */
|
|
1292
|
+
start() {
|
|
1293
|
+
if (this.destroyed || this.root) return;
|
|
1294
|
+
if (!this.container || typeof window === "undefined" || typeof document === "undefined") return;
|
|
1295
|
+
this.buildWidget();
|
|
1296
|
+
this.windowStart = performance.now();
|
|
1297
|
+
this.lastFrame = this.windowStart;
|
|
1298
|
+
this.busyMs = 0;
|
|
1299
|
+
this.rafId = requestAnimationFrame(this.onFrame);
|
|
1300
|
+
this.longTaskObserver = this.createLongTaskObserver();
|
|
1301
|
+
this.timer = setInterval(this.onTick, this.interval);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Render a fresh snapshot. Used by the internal ticker; also public so
|
|
1305
|
+
* tests and hosts can render known values (values are optional — a metric
|
|
1306
|
+
* without a value keeps its previous text).
|
|
1307
|
+
*/
|
|
1308
|
+
update(cpuPercent, ramBytes, ramLimitBytes) {
|
|
1309
|
+
if (!this.root) return;
|
|
1310
|
+
if (typeof cpuPercent === "number") {
|
|
1311
|
+
const clamped = Math.max(0, Math.min(100, cpuPercent));
|
|
1312
|
+
this.renderBar(this.cpuBar, clamped);
|
|
1313
|
+
if (this.cpuValue) this.cpuValue.textContent = `${Math.round(clamped)}%`;
|
|
1314
|
+
}
|
|
1315
|
+
if (typeof ramBytes === "number") {
|
|
1316
|
+
const pct = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? Math.max(0, Math.min(100, ramBytes / ramLimitBytes * 100)) : 0;
|
|
1317
|
+
this.renderBar(this.ramBar, pct);
|
|
1318
|
+
if (this.ramValue) {
|
|
1319
|
+
this.ramValue.textContent = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? `${formatMb(ramBytes)} / ${formatMb(ramLimitBytes)}` : formatMb(ramBytes);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/** Stop sampling, disconnect observers, and remove the widget. */
|
|
1324
|
+
destroy() {
|
|
1325
|
+
this.destroyed = true;
|
|
1326
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
1327
|
+
this.rafId = 0;
|
|
1328
|
+
if (this.timer) clearInterval(this.timer);
|
|
1329
|
+
this.timer = null;
|
|
1330
|
+
this.longTaskObserver?.disconnect();
|
|
1331
|
+
this.longTaskObserver = null;
|
|
1332
|
+
if (this.root && this.root.parentElement) {
|
|
1333
|
+
this.root.parentElement.removeChild(this.root);
|
|
1334
|
+
}
|
|
1335
|
+
this.root = null;
|
|
1336
|
+
}
|
|
1337
|
+
buildWidget() {
|
|
1338
|
+
const root = document.createElement("div");
|
|
1339
|
+
root.setAttribute("data-docflow-perf-monitor", "");
|
|
1340
|
+
root.style.cssText = [
|
|
1341
|
+
"position:fixed",
|
|
1342
|
+
"right:12px",
|
|
1343
|
+
"bottom:12px",
|
|
1344
|
+
"z-index:2147483000",
|
|
1345
|
+
"background:rgba(15,23,42,0.55)",
|
|
1346
|
+
"backdrop-filter:blur(6px)",
|
|
1347
|
+
"-webkit-backdrop-filter:blur(6px)",
|
|
1348
|
+
"color:rgba(226,232,240,0.9)",
|
|
1349
|
+
"font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",
|
|
1350
|
+
"padding:8px 10px",
|
|
1351
|
+
"border-radius:8px",
|
|
1352
|
+
"border:1px solid rgba(255,255,255,0.14)",
|
|
1353
|
+
"pointer-events:none",
|
|
1354
|
+
"user-select:none",
|
|
1355
|
+
"opacity:0.85",
|
|
1356
|
+
"box-shadow:0 4px 16px rgba(0,0,0,0.25)"
|
|
1357
|
+
].join(";");
|
|
1358
|
+
if (this.metrics.has("cpu")) {
|
|
1359
|
+
root.appendChild(this.buildRow("CPU", (el) => this.cpuBar = el, (el) => this.cpuValue = el));
|
|
1360
|
+
}
|
|
1361
|
+
if (this.metrics.has("ram")) {
|
|
1362
|
+
root.appendChild(this.buildRow("RAM", (el) => this.ramBar = el, (el) => this.ramValue = el));
|
|
1363
|
+
}
|
|
1364
|
+
this.root = root;
|
|
1365
|
+
this.container.appendChild(root);
|
|
1366
|
+
}
|
|
1367
|
+
buildRow(label, setBar, setValue) {
|
|
1368
|
+
const row = document.createElement("div");
|
|
1369
|
+
row.style.cssText = "display:flex;align-items:center;gap:6px;white-space:nowrap";
|
|
1370
|
+
const labelEl = document.createElement("span");
|
|
1371
|
+
labelEl.textContent = label;
|
|
1372
|
+
labelEl.style.cssText = "opacity:0.75;min-width:26px";
|
|
1373
|
+
const track = document.createElement("div");
|
|
1374
|
+
track.style.cssText = "width:46px;height:5px;border-radius:3px;background:rgba(255,255,255,0.18);overflow:hidden";
|
|
1375
|
+
const bar = document.createElement("div");
|
|
1376
|
+
bar.style.cssText = "width:0%;height:100%;border-radius:3px;background:#22d3ee;transition:width .3s ease";
|
|
1377
|
+
track.appendChild(bar);
|
|
1378
|
+
setBar(bar);
|
|
1379
|
+
const value = document.createElement("span");
|
|
1380
|
+
value.textContent = "\u2014";
|
|
1381
|
+
value.style.cssText = "min-width:64px;text-align:right";
|
|
1382
|
+
setValue(value);
|
|
1383
|
+
row.append(labelEl, track, value);
|
|
1384
|
+
return row;
|
|
1385
|
+
}
|
|
1386
|
+
renderBar(bar, pct) {
|
|
1387
|
+
if (!bar) return;
|
|
1388
|
+
bar.style.width = `${pct}%`;
|
|
1389
|
+
bar.style.background = pct >= 80 ? "#f87171" : pct >= 50 ? "#fbbf24" : "#22d3ee";
|
|
1390
|
+
}
|
|
1391
|
+
onFrame = (now) => {
|
|
1392
|
+
if (this.destroyed) return;
|
|
1393
|
+
const delta = now - this.lastFrame;
|
|
1394
|
+
this.lastFrame = now;
|
|
1395
|
+
if (delta > TARGET_FRAME_MS) {
|
|
1396
|
+
this.busyMs += delta - TARGET_FRAME_MS;
|
|
1397
|
+
}
|
|
1398
|
+
this.rafId = requestAnimationFrame(this.onFrame);
|
|
1399
|
+
};
|
|
1400
|
+
onTick = () => {
|
|
1401
|
+
if (this.destroyed) return;
|
|
1402
|
+
const now = performance.now();
|
|
1403
|
+
const windowMs = now - this.windowStart || this.interval;
|
|
1404
|
+
this.windowStart = now;
|
|
1405
|
+
let cpu;
|
|
1406
|
+
if (this.metrics.has("cpu")) {
|
|
1407
|
+
cpu = Math.max(0, Math.min(100, this.busyMs / windowMs * 100));
|
|
1408
|
+
this.busyMs = 0;
|
|
1409
|
+
}
|
|
1410
|
+
let ram;
|
|
1411
|
+
let ramLimit;
|
|
1412
|
+
if (this.metrics.has("ram")) {
|
|
1413
|
+
const memory = this.getMemoryInfo();
|
|
1414
|
+
if (memory) {
|
|
1415
|
+
ram = memory.usedJSHeapSize;
|
|
1416
|
+
ramLimit = memory.jsHeapSizeLimit;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
this.update(cpu, ram, ramLimit);
|
|
1420
|
+
};
|
|
1421
|
+
getMemoryInfo() {
|
|
1422
|
+
const perf = performance;
|
|
1423
|
+
const memory = perf.memory;
|
|
1424
|
+
if (!memory || typeof memory.usedJSHeapSize !== "number") return null;
|
|
1425
|
+
return memory;
|
|
1426
|
+
}
|
|
1427
|
+
createLongTaskObserver() {
|
|
1428
|
+
if (typeof PerformanceObserver === "undefined") return null;
|
|
1429
|
+
try {
|
|
1430
|
+
const observer = new PerformanceObserver((list) => {
|
|
1431
|
+
if (this.destroyed) return;
|
|
1432
|
+
for (const entry of list.getEntries()) {
|
|
1433
|
+
this.busyMs += entry.duration;
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
observer.observe({ entryTypes: ["longtask"] });
|
|
1437
|
+
return observer;
|
|
1438
|
+
} catch {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
function createPerformanceMonitor(options) {
|
|
1444
|
+
const monitor = new PerformanceMonitor(options);
|
|
1445
|
+
monitor.start();
|
|
1446
|
+
return monitor;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1265
1449
|
// src/Editor.ts
|
|
1266
1450
|
function sanitizePastedHTML(html) {
|
|
1267
1451
|
let cleaned = html.replace(/<meta[^>]*>/gi, "");
|
|
@@ -1339,6 +1523,7 @@ function createEditor(options = {}) {
|
|
|
1339
1523
|
};
|
|
1340
1524
|
const plugins = migratedOptions.plugins ?? [];
|
|
1341
1525
|
const collaborationSetup = migratedOptions.collaboration ? createCollaboration(migratedOptions.collaboration) : void 0;
|
|
1526
|
+
const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
|
|
1342
1527
|
let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
|
|
1343
1528
|
let pluginActions = createActionMap(tiptapEditor, plugins);
|
|
1344
1529
|
for (const plugin of plugins) {
|
|
@@ -1379,6 +1564,7 @@ function createEditor(options = {}) {
|
|
|
1379
1564
|
}
|
|
1380
1565
|
tiptapEditor.destroy();
|
|
1381
1566
|
collaborationSetup?.destroy();
|
|
1567
|
+
performanceMonitor?.destroy();
|
|
1382
1568
|
},
|
|
1383
1569
|
use: (plugin) => {
|
|
1384
1570
|
plugins.push(plugin);
|
|
@@ -1387,7 +1573,8 @@ function createEditor(options = {}) {
|
|
|
1387
1573
|
},
|
|
1388
1574
|
get pluginActions() {
|
|
1389
1575
|
return pluginActions;
|
|
1390
|
-
}
|
|
1576
|
+
},
|
|
1577
|
+
performanceMonitor
|
|
1391
1578
|
};
|
|
1392
1579
|
return editor;
|
|
1393
1580
|
}
|
|
@@ -2235,6 +2422,7 @@ function openaiCompatibleProvider(config) {
|
|
|
2235
2422
|
LOCAL_STORAGE_KEY,
|
|
2236
2423
|
PAGE_SIZES,
|
|
2237
2424
|
PaginationPlus,
|
|
2425
|
+
PerformanceMonitor,
|
|
2238
2426
|
SearchAndReplaceExtension,
|
|
2239
2427
|
SubdocumentProvider,
|
|
2240
2428
|
buildAIPrompt,
|
|
@@ -2244,6 +2432,7 @@ function openaiCompatibleProvider(config) {
|
|
|
2244
2432
|
createActionMap,
|
|
2245
2433
|
createCollaboration,
|
|
2246
2434
|
createEditor,
|
|
2435
|
+
createPerformanceMonitor,
|
|
2247
2436
|
definePlugin,
|
|
2248
2437
|
findMatches,
|
|
2249
2438
|
getSearchState,
|
package/dist/index.d.cts
CHANGED
|
@@ -307,6 +307,70 @@ type AIDraftFn = (req: {
|
|
|
307
307
|
k?: number;
|
|
308
308
|
}, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Lightweight client-side performance monitor (debug overlay).
|
|
312
|
+
*
|
|
313
|
+
* Shows current CPU utilization (event-loop busy time measured via
|
|
314
|
+
* `requestAnimationFrame` + the Long Tasks API) and JS heap usage in a
|
|
315
|
+
* semi-transparent widget pinned to the bottom-right corner of the viewport.
|
|
316
|
+
*
|
|
317
|
+
* The widget is opt-in and controlled by the host through the `debug: true`
|
|
318
|
+
* EditorOptions flag, so it never appears in production builds unless the
|
|
319
|
+
* consumer enables it. Because it is implemented with plain DOM + inline
|
|
320
|
+
* styles (no CSS imports, no framework), it works in every host surface the
|
|
321
|
+
* library ships: vanilla `createEditor()`, the Vue component, and the
|
|
322
|
+
* `<docs-editor>` Web Component (Shadow DOM).
|
|
323
|
+
*
|
|
324
|
+
* This is a pure debug *view* — it never reads or mutates the ProseMirror
|
|
325
|
+
* document, so the "single source of truth" and LIBRARY_CONTRACT rules hold.
|
|
326
|
+
*/
|
|
327
|
+
type MonitorMetric = 'cpu' | 'ram';
|
|
328
|
+
interface PerformanceMonitorOptions {
|
|
329
|
+
/** Element the widget is appended to. Defaults to `document.body`. */
|
|
330
|
+
container?: HTMLElement;
|
|
331
|
+
/** Refresh interval in ms. Defaults to 1000. */
|
|
332
|
+
interval?: number;
|
|
333
|
+
/** Which metrics to render. Defaults to `['cpu', 'ram']`. */
|
|
334
|
+
metrics?: MonitorMetric[];
|
|
335
|
+
}
|
|
336
|
+
declare class PerformanceMonitor {
|
|
337
|
+
private readonly container;
|
|
338
|
+
private readonly interval;
|
|
339
|
+
private readonly metrics;
|
|
340
|
+
private root;
|
|
341
|
+
private cpuBar;
|
|
342
|
+
private cpuValue;
|
|
343
|
+
private ramBar;
|
|
344
|
+
private ramValue;
|
|
345
|
+
private rafId;
|
|
346
|
+
private lastFrame;
|
|
347
|
+
private busyMs;
|
|
348
|
+
private windowStart;
|
|
349
|
+
private timer;
|
|
350
|
+
private longTaskObserver;
|
|
351
|
+
private destroyed;
|
|
352
|
+
constructor(options?: PerformanceMonitorOptions);
|
|
353
|
+
/** Append the widget to the container and start sampling. */
|
|
354
|
+
start(): void;
|
|
355
|
+
/**
|
|
356
|
+
* Render a fresh snapshot. Used by the internal ticker; also public so
|
|
357
|
+
* tests and hosts can render known values (values are optional — a metric
|
|
358
|
+
* without a value keeps its previous text).
|
|
359
|
+
*/
|
|
360
|
+
update(cpuPercent?: number, ramBytes?: number, ramLimitBytes?: number): void;
|
|
361
|
+
/** Stop sampling, disconnect observers, and remove the widget. */
|
|
362
|
+
destroy(): void;
|
|
363
|
+
private buildWidget;
|
|
364
|
+
private buildRow;
|
|
365
|
+
private renderBar;
|
|
366
|
+
private onFrame;
|
|
367
|
+
private onTick;
|
|
368
|
+
private getMemoryInfo;
|
|
369
|
+
private createLongTaskObserver;
|
|
370
|
+
}
|
|
371
|
+
/** Convenience factory: creates + starts a monitor (returns a stopped one when no DOM). */
|
|
372
|
+
declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
|
|
373
|
+
|
|
310
374
|
/**
|
|
311
375
|
* Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
|
|
312
376
|
* during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
|
|
@@ -337,6 +401,12 @@ interface EditorOptions {
|
|
|
337
401
|
aiStream?: AIStreamFn;
|
|
338
402
|
/** Host-injected cited-draft transport (Phase 7E — editor → server → RAG LLM). */
|
|
339
403
|
aiDraft?: AIDraftFn;
|
|
404
|
+
/**
|
|
405
|
+
* Enable the debug overlay (CPU + RAM monitor) pinned to the bottom-right
|
|
406
|
+
* corner of the viewport. Pure debug view — never touches document state.
|
|
407
|
+
* Defaults to `false`, so production consumers are unaffected.
|
|
408
|
+
*/
|
|
409
|
+
debug?: boolean;
|
|
340
410
|
}
|
|
341
411
|
interface DocsEditor {
|
|
342
412
|
editor: Editor;
|
|
@@ -346,6 +416,8 @@ interface DocsEditor {
|
|
|
346
416
|
destroy: () => void;
|
|
347
417
|
use: (plugin: DocsEditorPlugin) => void;
|
|
348
418
|
pluginActions: Record<string, (...args: unknown[]) => boolean>;
|
|
419
|
+
/** Active performance monitor when `debug: true` was set (undefined otherwise). */
|
|
420
|
+
performanceMonitor?: PerformanceMonitor;
|
|
349
421
|
}
|
|
350
422
|
declare function createEditor(options?: EditorOptions): DocsEditor;
|
|
351
423
|
|
|
@@ -608,4 +680,4 @@ interface OpenAICompatibleConfig {
|
|
|
608
680
|
}
|
|
609
681
|
declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
|
|
610
682
|
|
|
611
|
-
export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type OpenAICompatibleConfig, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
|
|
683
|
+
export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type MonitorMetric, type OpenAICompatibleConfig, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
|
package/dist/index.d.ts
CHANGED
|
@@ -307,6 +307,70 @@ type AIDraftFn = (req: {
|
|
|
307
307
|
k?: number;
|
|
308
308
|
}, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Lightweight client-side performance monitor (debug overlay).
|
|
312
|
+
*
|
|
313
|
+
* Shows current CPU utilization (event-loop busy time measured via
|
|
314
|
+
* `requestAnimationFrame` + the Long Tasks API) and JS heap usage in a
|
|
315
|
+
* semi-transparent widget pinned to the bottom-right corner of the viewport.
|
|
316
|
+
*
|
|
317
|
+
* The widget is opt-in and controlled by the host through the `debug: true`
|
|
318
|
+
* EditorOptions flag, so it never appears in production builds unless the
|
|
319
|
+
* consumer enables it. Because it is implemented with plain DOM + inline
|
|
320
|
+
* styles (no CSS imports, no framework), it works in every host surface the
|
|
321
|
+
* library ships: vanilla `createEditor()`, the Vue component, and the
|
|
322
|
+
* `<docs-editor>` Web Component (Shadow DOM).
|
|
323
|
+
*
|
|
324
|
+
* This is a pure debug *view* — it never reads or mutates the ProseMirror
|
|
325
|
+
* document, so the "single source of truth" and LIBRARY_CONTRACT rules hold.
|
|
326
|
+
*/
|
|
327
|
+
type MonitorMetric = 'cpu' | 'ram';
|
|
328
|
+
interface PerformanceMonitorOptions {
|
|
329
|
+
/** Element the widget is appended to. Defaults to `document.body`. */
|
|
330
|
+
container?: HTMLElement;
|
|
331
|
+
/** Refresh interval in ms. Defaults to 1000. */
|
|
332
|
+
interval?: number;
|
|
333
|
+
/** Which metrics to render. Defaults to `['cpu', 'ram']`. */
|
|
334
|
+
metrics?: MonitorMetric[];
|
|
335
|
+
}
|
|
336
|
+
declare class PerformanceMonitor {
|
|
337
|
+
private readonly container;
|
|
338
|
+
private readonly interval;
|
|
339
|
+
private readonly metrics;
|
|
340
|
+
private root;
|
|
341
|
+
private cpuBar;
|
|
342
|
+
private cpuValue;
|
|
343
|
+
private ramBar;
|
|
344
|
+
private ramValue;
|
|
345
|
+
private rafId;
|
|
346
|
+
private lastFrame;
|
|
347
|
+
private busyMs;
|
|
348
|
+
private windowStart;
|
|
349
|
+
private timer;
|
|
350
|
+
private longTaskObserver;
|
|
351
|
+
private destroyed;
|
|
352
|
+
constructor(options?: PerformanceMonitorOptions);
|
|
353
|
+
/** Append the widget to the container and start sampling. */
|
|
354
|
+
start(): void;
|
|
355
|
+
/**
|
|
356
|
+
* Render a fresh snapshot. Used by the internal ticker; also public so
|
|
357
|
+
* tests and hosts can render known values (values are optional — a metric
|
|
358
|
+
* without a value keeps its previous text).
|
|
359
|
+
*/
|
|
360
|
+
update(cpuPercent?: number, ramBytes?: number, ramLimitBytes?: number): void;
|
|
361
|
+
/** Stop sampling, disconnect observers, and remove the widget. */
|
|
362
|
+
destroy(): void;
|
|
363
|
+
private buildWidget;
|
|
364
|
+
private buildRow;
|
|
365
|
+
private renderBar;
|
|
366
|
+
private onFrame;
|
|
367
|
+
private onTick;
|
|
368
|
+
private getMemoryInfo;
|
|
369
|
+
private createLongTaskObserver;
|
|
370
|
+
}
|
|
371
|
+
/** Convenience factory: creates + starts a monitor (returns a stopped one when no DOM). */
|
|
372
|
+
declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
|
|
373
|
+
|
|
310
374
|
/**
|
|
311
375
|
* Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
|
|
312
376
|
* during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
|
|
@@ -337,6 +401,12 @@ interface EditorOptions {
|
|
|
337
401
|
aiStream?: AIStreamFn;
|
|
338
402
|
/** Host-injected cited-draft transport (Phase 7E — editor → server → RAG LLM). */
|
|
339
403
|
aiDraft?: AIDraftFn;
|
|
404
|
+
/**
|
|
405
|
+
* Enable the debug overlay (CPU + RAM monitor) pinned to the bottom-right
|
|
406
|
+
* corner of the viewport. Pure debug view — never touches document state.
|
|
407
|
+
* Defaults to `false`, so production consumers are unaffected.
|
|
408
|
+
*/
|
|
409
|
+
debug?: boolean;
|
|
340
410
|
}
|
|
341
411
|
interface DocsEditor {
|
|
342
412
|
editor: Editor;
|
|
@@ -346,6 +416,8 @@ interface DocsEditor {
|
|
|
346
416
|
destroy: () => void;
|
|
347
417
|
use: (plugin: DocsEditorPlugin) => void;
|
|
348
418
|
pluginActions: Record<string, (...args: unknown[]) => boolean>;
|
|
419
|
+
/** Active performance monitor when `debug: true` was set (undefined otherwise). */
|
|
420
|
+
performanceMonitor?: PerformanceMonitor;
|
|
349
421
|
}
|
|
350
422
|
declare function createEditor(options?: EditorOptions): DocsEditor;
|
|
351
423
|
|
|
@@ -608,4 +680,4 @@ interface OpenAICompatibleConfig {
|
|
|
608
680
|
}
|
|
609
681
|
declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
|
|
610
682
|
|
|
611
|
-
export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type OpenAICompatibleConfig, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
|
|
683
|
+
export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type MonitorMetric, type OpenAICompatibleConfig, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
|
package/dist/index.js
CHANGED
|
@@ -1193,6 +1193,188 @@ var PAGE_SIZES = {
|
|
|
1193
1193
|
TABLOID: TABLOID_PAGE_SIZE
|
|
1194
1194
|
};
|
|
1195
1195
|
|
|
1196
|
+
// src/PerformanceMonitor.ts
|
|
1197
|
+
var TARGET_FRAME_MS = 1e3 / 60;
|
|
1198
|
+
var formatMb = (bytes) => `${Math.round(bytes / 1024 / 1024)}MB`;
|
|
1199
|
+
var PerformanceMonitor = class {
|
|
1200
|
+
container;
|
|
1201
|
+
interval;
|
|
1202
|
+
metrics;
|
|
1203
|
+
root = null;
|
|
1204
|
+
cpuBar = null;
|
|
1205
|
+
cpuValue = null;
|
|
1206
|
+
ramBar = null;
|
|
1207
|
+
ramValue = null;
|
|
1208
|
+
rafId = 0;
|
|
1209
|
+
lastFrame = 0;
|
|
1210
|
+
busyMs = 0;
|
|
1211
|
+
windowStart = 0;
|
|
1212
|
+
timer = null;
|
|
1213
|
+
longTaskObserver = null;
|
|
1214
|
+
destroyed = false;
|
|
1215
|
+
constructor(options = {}) {
|
|
1216
|
+
this.container = options.container ?? (typeof document !== "undefined" ? document.body : void 0);
|
|
1217
|
+
this.interval = options.interval ?? 1e3;
|
|
1218
|
+
this.metrics = new Set(options.metrics ?? ["cpu", "ram"]);
|
|
1219
|
+
}
|
|
1220
|
+
/** Append the widget to the container and start sampling. */
|
|
1221
|
+
start() {
|
|
1222
|
+
if (this.destroyed || this.root) return;
|
|
1223
|
+
if (!this.container || typeof window === "undefined" || typeof document === "undefined") return;
|
|
1224
|
+
this.buildWidget();
|
|
1225
|
+
this.windowStart = performance.now();
|
|
1226
|
+
this.lastFrame = this.windowStart;
|
|
1227
|
+
this.busyMs = 0;
|
|
1228
|
+
this.rafId = requestAnimationFrame(this.onFrame);
|
|
1229
|
+
this.longTaskObserver = this.createLongTaskObserver();
|
|
1230
|
+
this.timer = setInterval(this.onTick, this.interval);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Render a fresh snapshot. Used by the internal ticker; also public so
|
|
1234
|
+
* tests and hosts can render known values (values are optional — a metric
|
|
1235
|
+
* without a value keeps its previous text).
|
|
1236
|
+
*/
|
|
1237
|
+
update(cpuPercent, ramBytes, ramLimitBytes) {
|
|
1238
|
+
if (!this.root) return;
|
|
1239
|
+
if (typeof cpuPercent === "number") {
|
|
1240
|
+
const clamped = Math.max(0, Math.min(100, cpuPercent));
|
|
1241
|
+
this.renderBar(this.cpuBar, clamped);
|
|
1242
|
+
if (this.cpuValue) this.cpuValue.textContent = `${Math.round(clamped)}%`;
|
|
1243
|
+
}
|
|
1244
|
+
if (typeof ramBytes === "number") {
|
|
1245
|
+
const pct = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? Math.max(0, Math.min(100, ramBytes / ramLimitBytes * 100)) : 0;
|
|
1246
|
+
this.renderBar(this.ramBar, pct);
|
|
1247
|
+
if (this.ramValue) {
|
|
1248
|
+
this.ramValue.textContent = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? `${formatMb(ramBytes)} / ${formatMb(ramLimitBytes)}` : formatMb(ramBytes);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
/** Stop sampling, disconnect observers, and remove the widget. */
|
|
1253
|
+
destroy() {
|
|
1254
|
+
this.destroyed = true;
|
|
1255
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
1256
|
+
this.rafId = 0;
|
|
1257
|
+
if (this.timer) clearInterval(this.timer);
|
|
1258
|
+
this.timer = null;
|
|
1259
|
+
this.longTaskObserver?.disconnect();
|
|
1260
|
+
this.longTaskObserver = null;
|
|
1261
|
+
if (this.root && this.root.parentElement) {
|
|
1262
|
+
this.root.parentElement.removeChild(this.root);
|
|
1263
|
+
}
|
|
1264
|
+
this.root = null;
|
|
1265
|
+
}
|
|
1266
|
+
buildWidget() {
|
|
1267
|
+
const root = document.createElement("div");
|
|
1268
|
+
root.setAttribute("data-docflow-perf-monitor", "");
|
|
1269
|
+
root.style.cssText = [
|
|
1270
|
+
"position:fixed",
|
|
1271
|
+
"right:12px",
|
|
1272
|
+
"bottom:12px",
|
|
1273
|
+
"z-index:2147483000",
|
|
1274
|
+
"background:rgba(15,23,42,0.55)",
|
|
1275
|
+
"backdrop-filter:blur(6px)",
|
|
1276
|
+
"-webkit-backdrop-filter:blur(6px)",
|
|
1277
|
+
"color:rgba(226,232,240,0.9)",
|
|
1278
|
+
"font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",
|
|
1279
|
+
"padding:8px 10px",
|
|
1280
|
+
"border-radius:8px",
|
|
1281
|
+
"border:1px solid rgba(255,255,255,0.14)",
|
|
1282
|
+
"pointer-events:none",
|
|
1283
|
+
"user-select:none",
|
|
1284
|
+
"opacity:0.85",
|
|
1285
|
+
"box-shadow:0 4px 16px rgba(0,0,0,0.25)"
|
|
1286
|
+
].join(";");
|
|
1287
|
+
if (this.metrics.has("cpu")) {
|
|
1288
|
+
root.appendChild(this.buildRow("CPU", (el) => this.cpuBar = el, (el) => this.cpuValue = el));
|
|
1289
|
+
}
|
|
1290
|
+
if (this.metrics.has("ram")) {
|
|
1291
|
+
root.appendChild(this.buildRow("RAM", (el) => this.ramBar = el, (el) => this.ramValue = el));
|
|
1292
|
+
}
|
|
1293
|
+
this.root = root;
|
|
1294
|
+
this.container.appendChild(root);
|
|
1295
|
+
}
|
|
1296
|
+
buildRow(label, setBar, setValue) {
|
|
1297
|
+
const row = document.createElement("div");
|
|
1298
|
+
row.style.cssText = "display:flex;align-items:center;gap:6px;white-space:nowrap";
|
|
1299
|
+
const labelEl = document.createElement("span");
|
|
1300
|
+
labelEl.textContent = label;
|
|
1301
|
+
labelEl.style.cssText = "opacity:0.75;min-width:26px";
|
|
1302
|
+
const track = document.createElement("div");
|
|
1303
|
+
track.style.cssText = "width:46px;height:5px;border-radius:3px;background:rgba(255,255,255,0.18);overflow:hidden";
|
|
1304
|
+
const bar = document.createElement("div");
|
|
1305
|
+
bar.style.cssText = "width:0%;height:100%;border-radius:3px;background:#22d3ee;transition:width .3s ease";
|
|
1306
|
+
track.appendChild(bar);
|
|
1307
|
+
setBar(bar);
|
|
1308
|
+
const value = document.createElement("span");
|
|
1309
|
+
value.textContent = "\u2014";
|
|
1310
|
+
value.style.cssText = "min-width:64px;text-align:right";
|
|
1311
|
+
setValue(value);
|
|
1312
|
+
row.append(labelEl, track, value);
|
|
1313
|
+
return row;
|
|
1314
|
+
}
|
|
1315
|
+
renderBar(bar, pct) {
|
|
1316
|
+
if (!bar) return;
|
|
1317
|
+
bar.style.width = `${pct}%`;
|
|
1318
|
+
bar.style.background = pct >= 80 ? "#f87171" : pct >= 50 ? "#fbbf24" : "#22d3ee";
|
|
1319
|
+
}
|
|
1320
|
+
onFrame = (now) => {
|
|
1321
|
+
if (this.destroyed) return;
|
|
1322
|
+
const delta = now - this.lastFrame;
|
|
1323
|
+
this.lastFrame = now;
|
|
1324
|
+
if (delta > TARGET_FRAME_MS) {
|
|
1325
|
+
this.busyMs += delta - TARGET_FRAME_MS;
|
|
1326
|
+
}
|
|
1327
|
+
this.rafId = requestAnimationFrame(this.onFrame);
|
|
1328
|
+
};
|
|
1329
|
+
onTick = () => {
|
|
1330
|
+
if (this.destroyed) return;
|
|
1331
|
+
const now = performance.now();
|
|
1332
|
+
const windowMs = now - this.windowStart || this.interval;
|
|
1333
|
+
this.windowStart = now;
|
|
1334
|
+
let cpu;
|
|
1335
|
+
if (this.metrics.has("cpu")) {
|
|
1336
|
+
cpu = Math.max(0, Math.min(100, this.busyMs / windowMs * 100));
|
|
1337
|
+
this.busyMs = 0;
|
|
1338
|
+
}
|
|
1339
|
+
let ram;
|
|
1340
|
+
let ramLimit;
|
|
1341
|
+
if (this.metrics.has("ram")) {
|
|
1342
|
+
const memory = this.getMemoryInfo();
|
|
1343
|
+
if (memory) {
|
|
1344
|
+
ram = memory.usedJSHeapSize;
|
|
1345
|
+
ramLimit = memory.jsHeapSizeLimit;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
this.update(cpu, ram, ramLimit);
|
|
1349
|
+
};
|
|
1350
|
+
getMemoryInfo() {
|
|
1351
|
+
const perf = performance;
|
|
1352
|
+
const memory = perf.memory;
|
|
1353
|
+
if (!memory || typeof memory.usedJSHeapSize !== "number") return null;
|
|
1354
|
+
return memory;
|
|
1355
|
+
}
|
|
1356
|
+
createLongTaskObserver() {
|
|
1357
|
+
if (typeof PerformanceObserver === "undefined") return null;
|
|
1358
|
+
try {
|
|
1359
|
+
const observer = new PerformanceObserver((list) => {
|
|
1360
|
+
if (this.destroyed) return;
|
|
1361
|
+
for (const entry of list.getEntries()) {
|
|
1362
|
+
this.busyMs += entry.duration;
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
observer.observe({ entryTypes: ["longtask"] });
|
|
1366
|
+
return observer;
|
|
1367
|
+
} catch {
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
function createPerformanceMonitor(options) {
|
|
1373
|
+
const monitor = new PerformanceMonitor(options);
|
|
1374
|
+
monitor.start();
|
|
1375
|
+
return monitor;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1196
1378
|
// src/Editor.ts
|
|
1197
1379
|
function sanitizePastedHTML(html) {
|
|
1198
1380
|
let cleaned = html.replace(/<meta[^>]*>/gi, "");
|
|
@@ -1270,6 +1452,7 @@ function createEditor(options = {}) {
|
|
|
1270
1452
|
};
|
|
1271
1453
|
const plugins = migratedOptions.plugins ?? [];
|
|
1272
1454
|
const collaborationSetup = migratedOptions.collaboration ? createCollaboration(migratedOptions.collaboration) : void 0;
|
|
1455
|
+
const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
|
|
1273
1456
|
let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
|
|
1274
1457
|
let pluginActions = createActionMap(tiptapEditor, plugins);
|
|
1275
1458
|
for (const plugin of plugins) {
|
|
@@ -1310,6 +1493,7 @@ function createEditor(options = {}) {
|
|
|
1310
1493
|
}
|
|
1311
1494
|
tiptapEditor.destroy();
|
|
1312
1495
|
collaborationSetup?.destroy();
|
|
1496
|
+
performanceMonitor?.destroy();
|
|
1313
1497
|
},
|
|
1314
1498
|
use: (plugin) => {
|
|
1315
1499
|
plugins.push(plugin);
|
|
@@ -1318,7 +1502,8 @@ function createEditor(options = {}) {
|
|
|
1318
1502
|
},
|
|
1319
1503
|
get pluginActions() {
|
|
1320
1504
|
return pluginActions;
|
|
1321
|
-
}
|
|
1505
|
+
},
|
|
1506
|
+
performanceMonitor
|
|
1322
1507
|
};
|
|
1323
1508
|
return editor;
|
|
1324
1509
|
}
|
|
@@ -2165,6 +2350,7 @@ export {
|
|
|
2165
2350
|
LOCAL_STORAGE_KEY,
|
|
2166
2351
|
PAGE_SIZES,
|
|
2167
2352
|
PaginationPlus,
|
|
2353
|
+
PerformanceMonitor,
|
|
2168
2354
|
SearchAndReplaceExtension,
|
|
2169
2355
|
SubdocumentProvider,
|
|
2170
2356
|
buildAIPrompt,
|
|
@@ -2174,6 +2360,7 @@ export {
|
|
|
2174
2360
|
createActionMap,
|
|
2175
2361
|
createCollaboration,
|
|
2176
2362
|
createEditor,
|
|
2363
|
+
createPerformanceMonitor,
|
|
2177
2364
|
definePlugin,
|
|
2178
2365
|
findMatches,
|
|
2179
2366
|
getSearchState,
|