@eyeglass/inspector 0.1.2 → 0.1.3
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.js +1362 -32
- package/dist/inspector.d.ts +15 -0
- package/dist/inspector.js +614 -11
- package/package.json +3 -2
package/dist/inspector.js
CHANGED
|
@@ -5,7 +5,32 @@ import { captureSnapshot } from './snapshot.js';
|
|
|
5
5
|
const BRIDGE_URL = 'http://localhost:3300';
|
|
6
6
|
const STORAGE_KEY = 'eyeglass_session';
|
|
7
7
|
const HISTORY_KEY = 'eyeglass_history';
|
|
8
|
+
const ENABLED_KEY = 'eyeglass_enabled';
|
|
9
|
+
const AUTOCOMMIT_KEY = 'eyeglass_autocommit';
|
|
8
10
|
const SESSION_TTL = 10000; // 10 seconds
|
|
11
|
+
// Fun rotating phrases for the "fixing" status
|
|
12
|
+
const WORKING_PHRASES = [
|
|
13
|
+
'Ruminating...',
|
|
14
|
+
'Percolating...',
|
|
15
|
+
'Divining...',
|
|
16
|
+
'Grokking...',
|
|
17
|
+
'Communing...',
|
|
18
|
+
'Concocting...',
|
|
19
|
+
'Synthesizing...',
|
|
20
|
+
'Distilling...',
|
|
21
|
+
'Incubating...',
|
|
22
|
+
'Forging...',
|
|
23
|
+
'Scrutinizing...',
|
|
24
|
+
'Triangulating...',
|
|
25
|
+
'Unraveling...',
|
|
26
|
+
'Traversing...',
|
|
27
|
+
'Sifting...',
|
|
28
|
+
'Marshaling...',
|
|
29
|
+
'Hydrating...',
|
|
30
|
+
'Harmonizing...',
|
|
31
|
+
'Indexing...',
|
|
32
|
+
'Entangling...',
|
|
33
|
+
];
|
|
9
34
|
// Eye cursor as base64-encoded SVG (16x16 eye icon, indigo color)
|
|
10
35
|
const EYE_CURSOR = `url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM2MzY2ZjEiIHN0cm9rZS13aWR0aD0iMi41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xIDEyczQtOCAxMS04IDExIDggMTEgOC00IDgtMTEgOC0xMS04LTExLTh6Ii8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyIgZmlsbD0iIzYzNjZmMSIvPjwvc3ZnPg==") 8 8, crosshair`;
|
|
11
36
|
const STYLES = `
|
|
@@ -22,7 +47,7 @@ const STYLES = `
|
|
|
22
47
|
font-size: 13px;
|
|
23
48
|
line-height: 1.5;
|
|
24
49
|
box-sizing: border-box;
|
|
25
|
-
--glass-bg: rgba(255, 255, 255, 0.
|
|
50
|
+
--glass-bg: rgba(255, 255, 255, 0.88);
|
|
26
51
|
--glass-border: rgba(0, 0, 0, 0.25);
|
|
27
52
|
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
28
53
|
--divider: rgba(0, 0, 0, 0.18);
|
|
@@ -371,6 +396,37 @@ const STYLES = `
|
|
|
371
396
|
color: var(--accent);
|
|
372
397
|
}
|
|
373
398
|
|
|
399
|
+
/* Skeleton loader */
|
|
400
|
+
.skeleton-item {
|
|
401
|
+
padding: 8px 16px;
|
|
402
|
+
display: flex;
|
|
403
|
+
gap: 10px;
|
|
404
|
+
align-items: center;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.skeleton-icon {
|
|
408
|
+
width: 20px;
|
|
409
|
+
height: 20px;
|
|
410
|
+
border-radius: 50%;
|
|
411
|
+
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
|
|
412
|
+
background-size: 200% 100%;
|
|
413
|
+
animation: shimmer 1.5s infinite;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.skeleton-line {
|
|
417
|
+
height: 14px;
|
|
418
|
+
border-radius: 4px;
|
|
419
|
+
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
|
|
420
|
+
background-size: 200% 100%;
|
|
421
|
+
animation: shimmer 1.5s infinite;
|
|
422
|
+
width: 60%;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@keyframes shimmer {
|
|
426
|
+
0% { background-position: 200% 0; }
|
|
427
|
+
100% { background-position: -200% 0; }
|
|
428
|
+
}
|
|
429
|
+
|
|
374
430
|
/* Status Footer */
|
|
375
431
|
.panel-footer {
|
|
376
432
|
padding: 10px 16px;
|
|
@@ -549,7 +605,7 @@ const STYLES = `
|
|
|
549
605
|
}
|
|
550
606
|
}
|
|
551
607
|
|
|
552
|
-
.hub.disabled {
|
|
608
|
+
.hub.disabled.collapsed {
|
|
553
609
|
opacity: 0.5;
|
|
554
610
|
}
|
|
555
611
|
|
|
@@ -754,6 +810,107 @@ const STYLES = `
|
|
|
754
810
|
color: var(--text-muted);
|
|
755
811
|
}
|
|
756
812
|
|
|
813
|
+
.hub-settings-btn {
|
|
814
|
+
width: 20px;
|
|
815
|
+
height: 20px;
|
|
816
|
+
border: none;
|
|
817
|
+
background: transparent;
|
|
818
|
+
color: var(--text-muted);
|
|
819
|
+
cursor: pointer;
|
|
820
|
+
border-radius: 4px;
|
|
821
|
+
display: flex;
|
|
822
|
+
align-items: center;
|
|
823
|
+
justify-content: center;
|
|
824
|
+
transition: all 0.15s;
|
|
825
|
+
flex-shrink: 0;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.hub-settings-btn:hover {
|
|
829
|
+
background: rgba(0, 0, 0, 0.05);
|
|
830
|
+
color: var(--text-secondary);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.hub-settings-btn svg {
|
|
834
|
+
width: 12px;
|
|
835
|
+
height: 12px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/* Settings Page */
|
|
839
|
+
.hub-settings-page {
|
|
840
|
+
padding: 8px 0;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.hub-settings-header {
|
|
844
|
+
display: flex;
|
|
845
|
+
align-items: center;
|
|
846
|
+
gap: 6px;
|
|
847
|
+
padding: 4px 8px 8px;
|
|
848
|
+
border-bottom: 1px solid var(--divider);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.hub-back-btn {
|
|
852
|
+
width: 20px;
|
|
853
|
+
height: 20px;
|
|
854
|
+
border: none;
|
|
855
|
+
background: transparent;
|
|
856
|
+
color: var(--text-secondary);
|
|
857
|
+
cursor: pointer;
|
|
858
|
+
border-radius: 4px;
|
|
859
|
+
display: flex;
|
|
860
|
+
align-items: center;
|
|
861
|
+
justify-content: center;
|
|
862
|
+
transition: all 0.15s;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.hub-back-btn:hover {
|
|
866
|
+
background: rgba(0, 0, 0, 0.05);
|
|
867
|
+
color: var(--text-primary);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.hub-settings-title {
|
|
871
|
+
font-size: 11px;
|
|
872
|
+
font-weight: 600;
|
|
873
|
+
color: var(--text-primary);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.hub-settings-list {
|
|
877
|
+
padding: 8px;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.hub-setting-row {
|
|
881
|
+
display: flex;
|
|
882
|
+
align-items: center;
|
|
883
|
+
justify-content: space-between;
|
|
884
|
+
gap: 8px;
|
|
885
|
+
padding: 6px 0;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.hub-setting-row:not(:last-child) {
|
|
889
|
+
border-bottom: 1px solid var(--divider);
|
|
890
|
+
padding-bottom: 10px;
|
|
891
|
+
margin-bottom: 4px;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.hub-setting-info {
|
|
895
|
+
flex: 1;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.hub-setting-label {
|
|
899
|
+
font-size: 11px;
|
|
900
|
+
font-weight: 500;
|
|
901
|
+
color: var(--text-primary);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.hub-setting-desc {
|
|
905
|
+
font-size: 9px;
|
|
906
|
+
color: var(--text-muted);
|
|
907
|
+
margin-top: 2px;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.hub-button-group {
|
|
911
|
+
display: flex;
|
|
912
|
+
}
|
|
913
|
+
|
|
757
914
|
/* Collapsed hub (minimal) */
|
|
758
915
|
.hub.collapsed .hub-title,
|
|
759
916
|
.hub.collapsed .hub-toggle {
|
|
@@ -768,6 +925,156 @@ const STYLES = `
|
|
|
768
925
|
gap: 4px;
|
|
769
926
|
}
|
|
770
927
|
|
|
928
|
+
.toggle-switch {
|
|
929
|
+
position: relative;
|
|
930
|
+
width: 32px;
|
|
931
|
+
height: 18px;
|
|
932
|
+
background: #cbd5e1;
|
|
933
|
+
border-radius: 9px;
|
|
934
|
+
cursor: pointer;
|
|
935
|
+
transition: background 0.2s;
|
|
936
|
+
border: none;
|
|
937
|
+
padding: 0;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
.toggle-switch.active {
|
|
941
|
+
background: var(--accent);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.toggle-switch::after {
|
|
945
|
+
content: '';
|
|
946
|
+
position: absolute;
|
|
947
|
+
top: 2px;
|
|
948
|
+
left: 2px;
|
|
949
|
+
width: 14px;
|
|
950
|
+
height: 14px;
|
|
951
|
+
background: white;
|
|
952
|
+
border-radius: 50%;
|
|
953
|
+
transition: transform 0.2s;
|
|
954
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.toggle-switch.active::after {
|
|
958
|
+
transform: translateX(14px);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/* Success action buttons */
|
|
962
|
+
.success-actions {
|
|
963
|
+
display: flex;
|
|
964
|
+
gap: 6px;
|
|
965
|
+
margin-left: auto;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.action-btn {
|
|
969
|
+
padding: 4px 10px;
|
|
970
|
+
border: none;
|
|
971
|
+
border-radius: 6px;
|
|
972
|
+
font-size: 11px;
|
|
973
|
+
font-weight: 600;
|
|
974
|
+
font-family: inherit;
|
|
975
|
+
cursor: pointer;
|
|
976
|
+
transition: all 0.15s;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.action-btn-commit {
|
|
980
|
+
background: var(--success);
|
|
981
|
+
color: white;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.action-btn-commit:hover {
|
|
985
|
+
background: #059669;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.action-btn-undo {
|
|
989
|
+
background: rgba(239, 68, 68, 0.1);
|
|
990
|
+
color: var(--error);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.action-btn-undo:hover {
|
|
994
|
+
background: rgba(239, 68, 68, 0.2);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/* Follow-up input */
|
|
998
|
+
.followup-area {
|
|
999
|
+
padding: 12px;
|
|
1000
|
+
border-top: 1px solid var(--divider);
|
|
1001
|
+
background: rgba(16, 185, 129, 0.04);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.followup-row {
|
|
1005
|
+
display: flex;
|
|
1006
|
+
gap: 8px;
|
|
1007
|
+
align-items: center;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.followup-input {
|
|
1011
|
+
flex: 1;
|
|
1012
|
+
padding: 8px 12px;
|
|
1013
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
1014
|
+
border-radius: var(--border-radius-sm);
|
|
1015
|
+
font-size: 12px;
|
|
1016
|
+
font-family: inherit;
|
|
1017
|
+
background: rgba(255, 255, 255, 0.8);
|
|
1018
|
+
color: var(--text-primary);
|
|
1019
|
+
outline: none;
|
|
1020
|
+
transition: all 0.15s;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.followup-input::placeholder {
|
|
1024
|
+
color: var(--text-muted);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.followup-input:focus {
|
|
1028
|
+
border-color: var(--accent);
|
|
1029
|
+
box-shadow: 0 0 0 2px var(--accent-soft);
|
|
1030
|
+
background: white;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.followup-send {
|
|
1034
|
+
flex-shrink: 0;
|
|
1035
|
+
padding: 9px 16px;
|
|
1036
|
+
border: 1px solid transparent;
|
|
1037
|
+
border-radius: var(--border-radius-sm);
|
|
1038
|
+
background: var(--accent);
|
|
1039
|
+
color: white;
|
|
1040
|
+
font-size: 12px;
|
|
1041
|
+
font-weight: 600;
|
|
1042
|
+
font-family: inherit;
|
|
1043
|
+
cursor: pointer;
|
|
1044
|
+
transition: all 0.15s;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.followup-send:hover {
|
|
1048
|
+
background: #4f46e5;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.followup-send:disabled {
|
|
1052
|
+
opacity: 0.5;
|
|
1053
|
+
cursor: not-allowed;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
.followup-done {
|
|
1057
|
+
flex-shrink: 0;
|
|
1058
|
+
width: 32px;
|
|
1059
|
+
height: 32px;
|
|
1060
|
+
padding: 0;
|
|
1061
|
+
border: 1px solid var(--divider);
|
|
1062
|
+
border-radius: var(--border-radius-sm);
|
|
1063
|
+
background: transparent;
|
|
1064
|
+
color: var(--text-secondary);
|
|
1065
|
+
font-size: 14px;
|
|
1066
|
+
font-family: inherit;
|
|
1067
|
+
cursor: pointer;
|
|
1068
|
+
transition: all 0.15s;
|
|
1069
|
+
display: flex;
|
|
1070
|
+
align-items: center;
|
|
1071
|
+
justify-content: center;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.followup-done:hover {
|
|
1075
|
+
background: rgba(0, 0, 0, 0.04);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
771
1078
|
/* Multi-select styles */
|
|
772
1079
|
.highlight.multi {
|
|
773
1080
|
border-style: dashed;
|
|
@@ -919,7 +1226,9 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
919
1226
|
this.activityEvents = [];
|
|
920
1227
|
this.currentStatus = 'idle';
|
|
921
1228
|
this.hubExpanded = false;
|
|
1229
|
+
this.hubPage = 'main';
|
|
922
1230
|
this.inspectorEnabled = true;
|
|
1231
|
+
this.autoCommitEnabled = true;
|
|
923
1232
|
this.history = [];
|
|
924
1233
|
this.isDragging = false;
|
|
925
1234
|
this.dragOffset = { x: 0, y: 0 };
|
|
@@ -934,6 +1243,9 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
934
1243
|
this.cursorStyleElement = null;
|
|
935
1244
|
// Scroll handling
|
|
936
1245
|
this.scrollTimeout = null;
|
|
1246
|
+
// Rotating status phrases
|
|
1247
|
+
this.phraseIndex = 0;
|
|
1248
|
+
this.phraseInterval = null;
|
|
937
1249
|
this.handlePanelDrag = (e) => {
|
|
938
1250
|
if (!this.isDragging || !this.panel)
|
|
939
1251
|
return;
|
|
@@ -967,6 +1279,8 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
967
1279
|
document.addEventListener('click', this.handleClick, true);
|
|
968
1280
|
document.addEventListener('keydown', this.handleKeyDown, true);
|
|
969
1281
|
window.addEventListener('scroll', this.handleScroll, true);
|
|
1282
|
+
this.loadEnabledState();
|
|
1283
|
+
this.loadAutoCommitState();
|
|
970
1284
|
this.loadHistory();
|
|
971
1285
|
this.renderHub();
|
|
972
1286
|
this.connectSSE();
|
|
@@ -1038,6 +1352,44 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1038
1352
|
this.toast = null;
|
|
1039
1353
|
}
|
|
1040
1354
|
}
|
|
1355
|
+
loadEnabledState() {
|
|
1356
|
+
try {
|
|
1357
|
+
const stored = localStorage.getItem(ENABLED_KEY);
|
|
1358
|
+
if (stored !== null) {
|
|
1359
|
+
this.inspectorEnabled = stored === 'true';
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
catch (e) {
|
|
1363
|
+
// Ignore storage errors
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
saveEnabledState() {
|
|
1367
|
+
try {
|
|
1368
|
+
localStorage.setItem(ENABLED_KEY, String(this.inspectorEnabled));
|
|
1369
|
+
}
|
|
1370
|
+
catch (e) {
|
|
1371
|
+
// Ignore storage errors
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
loadAutoCommitState() {
|
|
1375
|
+
try {
|
|
1376
|
+
const stored = localStorage.getItem(AUTOCOMMIT_KEY);
|
|
1377
|
+
if (stored !== null) {
|
|
1378
|
+
this.autoCommitEnabled = stored === 'true';
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
catch (e) {
|
|
1382
|
+
// Ignore storage errors
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
saveAutoCommitState() {
|
|
1386
|
+
try {
|
|
1387
|
+
localStorage.setItem(AUTOCOMMIT_KEY, String(this.autoCommitEnabled));
|
|
1388
|
+
}
|
|
1389
|
+
catch (e) {
|
|
1390
|
+
// Ignore storage errors
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1041
1393
|
loadHistory() {
|
|
1042
1394
|
try {
|
|
1043
1395
|
const stored = sessionStorage.getItem(HISTORY_KEY);
|
|
@@ -1087,6 +1439,16 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1087
1439
|
this.hub.className = 'hub';
|
|
1088
1440
|
this.shadow.appendChild(this.hub);
|
|
1089
1441
|
}
|
|
1442
|
+
if (this.hubPage === 'settings') {
|
|
1443
|
+
this.renderHubSettingsPage();
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
this.renderHubMainPage();
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
renderHubMainPage() {
|
|
1450
|
+
if (!this.hub)
|
|
1451
|
+
return;
|
|
1090
1452
|
const collapsedClass = this.hubExpanded ? '' : 'collapsed';
|
|
1091
1453
|
const disabledClass = this.inspectorEnabled ? '' : 'disabled';
|
|
1092
1454
|
const expandedClass = this.hubExpanded ? 'expanded' : '';
|
|
@@ -1094,6 +1456,7 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1094
1456
|
this.hub.className = `hub ${collapsedClass} ${disabledClass}`.trim();
|
|
1095
1457
|
const eyeOpenSvg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`;
|
|
1096
1458
|
const eyeClosedSvg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>`;
|
|
1459
|
+
const gearSvg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;
|
|
1097
1460
|
this.hub.innerHTML = `
|
|
1098
1461
|
<div class="hub-header">
|
|
1099
1462
|
<div class="hub-header-left">
|
|
@@ -1102,9 +1465,12 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1102
1465
|
${activeCount > 0 ? `<span class="hub-badge">${activeCount}</span>` : ''}
|
|
1103
1466
|
<button class="hub-toggle ${expandedClass}" title="Toggle history">▼</button>
|
|
1104
1467
|
</div>
|
|
1468
|
+
<div class="hub-button-group">
|
|
1469
|
+
<button class="hub-settings-btn" title="Settings">${gearSvg}</button>
|
|
1105
1470
|
<button class="hub-disable ${this.inspectorEnabled ? 'active' : ''}" title="${this.inspectorEnabled ? 'Disable' : 'Enable'} inspector">
|
|
1106
1471
|
${this.inspectorEnabled ? eyeOpenSvg : eyeClosedSvg}
|
|
1107
1472
|
</button>
|
|
1473
|
+
</div>
|
|
1108
1474
|
</div>
|
|
1109
1475
|
<div class="hub-content ${expandedClass}">
|
|
1110
1476
|
${this.history.length > 0 ? `
|
|
@@ -1129,19 +1495,27 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1129
1495
|
`;
|
|
1130
1496
|
// Wire up event handlers
|
|
1131
1497
|
const header = this.hub.querySelector('.hub-header');
|
|
1132
|
-
const toggleBtn = this.hub.querySelector('.hub-toggle');
|
|
1133
1498
|
const disableBtn = this.hub.querySelector('.hub-disable');
|
|
1134
|
-
|
|
1499
|
+
const settingsBtn = this.hub.querySelector('.hub-settings-btn');
|
|
1500
|
+
// Toggle expand/collapse on header click (except buttons)
|
|
1135
1501
|
header.addEventListener('click', (e) => {
|
|
1136
|
-
if (e.target === disableBtn)
|
|
1502
|
+
if (e.target === disableBtn || e.target === settingsBtn || e.target.closest('.hub-settings-btn'))
|
|
1137
1503
|
return;
|
|
1138
1504
|
this.hubExpanded = !this.hubExpanded;
|
|
1139
1505
|
this.renderHub();
|
|
1140
1506
|
});
|
|
1507
|
+
// Open settings page
|
|
1508
|
+
settingsBtn.addEventListener('click', (e) => {
|
|
1509
|
+
e.stopPropagation();
|
|
1510
|
+
this.hubPage = 'settings';
|
|
1511
|
+
this.hubExpanded = true; // Ensure expanded when viewing settings
|
|
1512
|
+
this.renderHub();
|
|
1513
|
+
});
|
|
1141
1514
|
// Toggle inspector enabled state
|
|
1142
1515
|
disableBtn.addEventListener('click', (e) => {
|
|
1143
1516
|
e.stopPropagation();
|
|
1144
1517
|
this.inspectorEnabled = !this.inspectorEnabled;
|
|
1518
|
+
this.saveEnabledState();
|
|
1145
1519
|
if (!this.inspectorEnabled) {
|
|
1146
1520
|
this.unfreeze();
|
|
1147
1521
|
}
|
|
@@ -1157,6 +1531,53 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1157
1531
|
});
|
|
1158
1532
|
});
|
|
1159
1533
|
}
|
|
1534
|
+
renderHubSettingsPage() {
|
|
1535
|
+
if (!this.hub)
|
|
1536
|
+
return;
|
|
1537
|
+
this.hub.className = 'hub';
|
|
1538
|
+
this.hub.innerHTML = `
|
|
1539
|
+
<div class="hub-header">
|
|
1540
|
+
<div class="hub-header-left">
|
|
1541
|
+
<div class="hub-logo">👁</div>
|
|
1542
|
+
<span class="hub-title">Eyeglass</span>
|
|
1543
|
+
</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
<div class="hub-content expanded">
|
|
1546
|
+
<div class="hub-settings-page">
|
|
1547
|
+
<div class="hub-settings-header">
|
|
1548
|
+
<button class="hub-back-btn" title="Back">←</button>
|
|
1549
|
+
<span class="hub-settings-title">Settings</span>
|
|
1550
|
+
</div>
|
|
1551
|
+
<div class="hub-settings-list">
|
|
1552
|
+
<div class="hub-setting-row">
|
|
1553
|
+
<div class="hub-setting-info">
|
|
1554
|
+
<div class="hub-setting-label">Auto-commit</div>
|
|
1555
|
+
<div class="hub-setting-desc">Automatically commit changes on success</div>
|
|
1556
|
+
</div>
|
|
1557
|
+
<button class="toggle-switch ${this.autoCommitEnabled ? 'active' : ''}" data-setting="autoCommit"></button>
|
|
1558
|
+
</div>
|
|
1559
|
+
</div>
|
|
1560
|
+
</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
`;
|
|
1563
|
+
// Wire up back button
|
|
1564
|
+
const backBtn = this.hub.querySelector('.hub-back-btn');
|
|
1565
|
+
backBtn.addEventListener('click', () => {
|
|
1566
|
+
this.hubPage = 'main';
|
|
1567
|
+
this.renderHub();
|
|
1568
|
+
});
|
|
1569
|
+
// Wire up toggle switches
|
|
1570
|
+
this.hub.querySelectorAll('.toggle-switch').forEach(btn => {
|
|
1571
|
+
btn.addEventListener('click', (e) => {
|
|
1572
|
+
const setting = e.currentTarget.dataset.setting;
|
|
1573
|
+
if (setting === 'autoCommit') {
|
|
1574
|
+
this.autoCommitEnabled = !this.autoCommitEnabled;
|
|
1575
|
+
this.saveAutoCommitState();
|
|
1576
|
+
this.renderHub();
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1160
1581
|
async requestUndo(interactionId) {
|
|
1161
1582
|
const itemIndex = this.history.findIndex(h => h.interactionId === interactionId);
|
|
1162
1583
|
if (itemIndex === -1)
|
|
@@ -1194,6 +1615,32 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1194
1615
|
console.warn('Undo request failed:', err);
|
|
1195
1616
|
}
|
|
1196
1617
|
}
|
|
1618
|
+
async requestCommit(interactionId) {
|
|
1619
|
+
const itemIndex = this.history.findIndex(h => h.interactionId === interactionId);
|
|
1620
|
+
try {
|
|
1621
|
+
const response = await fetch(`${BRIDGE_URL}/commit`, {
|
|
1622
|
+
method: 'POST',
|
|
1623
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1624
|
+
body: JSON.stringify({ interactionId }),
|
|
1625
|
+
});
|
|
1626
|
+
if (response.ok) {
|
|
1627
|
+
// Update status to show it's committed
|
|
1628
|
+
if (itemIndex >= 0) {
|
|
1629
|
+
this.history[itemIndex].status = 'success';
|
|
1630
|
+
this.saveHistory();
|
|
1631
|
+
this.renderHub();
|
|
1632
|
+
}
|
|
1633
|
+
// Close the panel after commit
|
|
1634
|
+
this.unfreeze();
|
|
1635
|
+
}
|
|
1636
|
+
else {
|
|
1637
|
+
console.warn('Commit request failed');
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
console.warn('Commit request failed:', err);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1197
1644
|
disconnectedCallback() {
|
|
1198
1645
|
document.removeEventListener('mousemove', this.handleMouseMove, true);
|
|
1199
1646
|
document.removeEventListener('click', this.handleClick, true);
|
|
@@ -1236,9 +1683,18 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1236
1683
|
this.currentStatus = event.status;
|
|
1237
1684
|
// Persist session so we can show result after page reload
|
|
1238
1685
|
this.saveSession(event.message);
|
|
1239
|
-
|
|
1686
|
+
// Manage phrase rotation based on status
|
|
1687
|
+
if (event.status === 'fixing') {
|
|
1688
|
+
this.startPhraseRotation();
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
this.stopPhraseRotation();
|
|
1692
|
+
}
|
|
1693
|
+
if (event.status === 'failed') {
|
|
1694
|
+
// Auto-close on failure after delay
|
|
1240
1695
|
setTimeout(() => this.unfreeze(), 4000);
|
|
1241
1696
|
}
|
|
1697
|
+
// Don't auto-close on success - show follow-up UI instead
|
|
1242
1698
|
}
|
|
1243
1699
|
this.renderPanel();
|
|
1244
1700
|
}
|
|
@@ -1498,6 +1954,8 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1498
1954
|
this.selectedSnapshots = [];
|
|
1499
1955
|
this.submittedSnapshots = [];
|
|
1500
1956
|
this.clearMultiSelectHighlights();
|
|
1957
|
+
// Stop phrase rotation
|
|
1958
|
+
this.stopPhraseRotation();
|
|
1501
1959
|
this.hidePanel();
|
|
1502
1960
|
this.hideHighlight();
|
|
1503
1961
|
this.updateCursor();
|
|
@@ -1643,6 +2101,8 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1643
2101
|
? (this.panel.querySelector('.user-request-text')?.textContent || '')
|
|
1644
2102
|
: '';
|
|
1645
2103
|
const isDone = this.currentStatus === 'success' || this.currentStatus === 'failed';
|
|
2104
|
+
const showActionButtons = this.currentStatus === 'success' && !this.autoCommitEnabled;
|
|
2105
|
+
const showFollowUp = this.currentStatus === 'success';
|
|
1646
2106
|
// Build header display based on submitted snapshots
|
|
1647
2107
|
const snapshotCount = this.submittedSnapshots.length;
|
|
1648
2108
|
const headerDisplay = snapshotCount > 1
|
|
@@ -1664,7 +2124,22 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1664
2124
|
<div class="panel-footer ${isDone ? 'done' : ''}">
|
|
1665
2125
|
<div class="status-indicator ${this.currentStatus}"></div>
|
|
1666
2126
|
<span class="status-text">${this.getStatusText()}</span>
|
|
2127
|
+
${showActionButtons ? `
|
|
2128
|
+
<div class="success-actions">
|
|
2129
|
+
<button class="action-btn action-btn-undo" title="Discard changes">Undo</button>
|
|
2130
|
+
<button class="action-btn action-btn-commit" title="Commit changes">Commit</button>
|
|
2131
|
+
</div>
|
|
2132
|
+
` : ''}
|
|
1667
2133
|
</div>
|
|
2134
|
+
${showFollowUp ? `
|
|
2135
|
+
<div class="followup-area">
|
|
2136
|
+
<div class="followup-row">
|
|
2137
|
+
<input type="text" class="followup-input" placeholder="Anything else?" />
|
|
2138
|
+
<button class="followup-send">Send</button>
|
|
2139
|
+
<button class="followup-done">✕</button>
|
|
2140
|
+
</div>
|
|
2141
|
+
</div>
|
|
2142
|
+
` : ''}
|
|
1668
2143
|
`;
|
|
1669
2144
|
const closeBtn = this.panel.querySelector('.close-btn');
|
|
1670
2145
|
closeBtn.addEventListener('click', () => this.unfreeze());
|
|
@@ -1681,6 +2156,36 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1681
2156
|
this.submitAnswer(questionId, answerId, answerLabel);
|
|
1682
2157
|
});
|
|
1683
2158
|
});
|
|
2159
|
+
// Wire up action buttons (commit/undo) if present
|
|
2160
|
+
const commitBtn = this.panel.querySelector('.action-btn-commit');
|
|
2161
|
+
const undoBtn = this.panel.querySelector('.action-btn-undo');
|
|
2162
|
+
if (commitBtn && this.interactionId) {
|
|
2163
|
+
commitBtn.addEventListener('click', () => this.requestCommit(this.interactionId));
|
|
2164
|
+
}
|
|
2165
|
+
if (undoBtn && this.interactionId) {
|
|
2166
|
+
undoBtn.addEventListener('click', () => this.requestUndo(this.interactionId));
|
|
2167
|
+
}
|
|
2168
|
+
// Wire up follow-up input if present
|
|
2169
|
+
const followupInput = this.panel.querySelector('.followup-input');
|
|
2170
|
+
const followupSend = this.panel.querySelector('.followup-send');
|
|
2171
|
+
const followupDone = this.panel.querySelector('.followup-done');
|
|
2172
|
+
if (followupInput && followupSend) {
|
|
2173
|
+
followupSend.addEventListener('click', () => {
|
|
2174
|
+
if (followupInput.value.trim()) {
|
|
2175
|
+
this.submitFollowUp(followupInput.value);
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
followupInput.addEventListener('keydown', (e) => {
|
|
2179
|
+
if (e.key === 'Enter' && followupInput.value.trim()) {
|
|
2180
|
+
this.submitFollowUp(followupInput.value);
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
// Focus the follow-up input
|
|
2184
|
+
requestAnimationFrame(() => followupInput.focus());
|
|
2185
|
+
}
|
|
2186
|
+
if (followupDone) {
|
|
2187
|
+
followupDone.addEventListener('click', () => this.unfreeze());
|
|
2188
|
+
}
|
|
1684
2189
|
// Scroll to bottom of activity feed
|
|
1685
2190
|
const feed = this.panel.querySelector('.activity-feed');
|
|
1686
2191
|
if (feed) {
|
|
@@ -1688,12 +2193,17 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1688
2193
|
}
|
|
1689
2194
|
}
|
|
1690
2195
|
renderActivityFeed() {
|
|
1691
|
-
|
|
2196
|
+
const items = this.activityEvents.map((event) => {
|
|
1692
2197
|
switch (event.type) {
|
|
1693
2198
|
case 'status':
|
|
1694
|
-
// Skip pending
|
|
1695
|
-
if (event.status === 'pending'
|
|
2199
|
+
// Skip pending - shown in footer. Show fixing only if it has a meaningful message
|
|
2200
|
+
if (event.status === 'pending')
|
|
1696
2201
|
return '';
|
|
2202
|
+
if (event.status === 'fixing') {
|
|
2203
|
+
// Skip generic/missing messages - we have rotating phrases in the footer
|
|
2204
|
+
if (!event.message || event.message === 'Agent is working...')
|
|
2205
|
+
return '';
|
|
2206
|
+
}
|
|
1697
2207
|
return this.renderStatusItem(event);
|
|
1698
2208
|
case 'thought':
|
|
1699
2209
|
return this.renderThoughtItem(event);
|
|
@@ -1704,7 +2214,17 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1704
2214
|
default:
|
|
1705
2215
|
return '';
|
|
1706
2216
|
}
|
|
1707
|
-
}).
|
|
2217
|
+
}).filter(Boolean);
|
|
2218
|
+
// Show skeleton while waiting for first meaningful activity
|
|
2219
|
+
if (items.length === 0 && (this.currentStatus === 'pending' || this.currentStatus === 'fixing')) {
|
|
2220
|
+
return `
|
|
2221
|
+
<div class="skeleton-item">
|
|
2222
|
+
<div class="skeleton-icon"></div>
|
|
2223
|
+
<div class="skeleton-line"></div>
|
|
2224
|
+
</div>
|
|
2225
|
+
`;
|
|
2226
|
+
}
|
|
2227
|
+
return items.join('');
|
|
1708
2228
|
}
|
|
1709
2229
|
renderStatusItem(event) {
|
|
1710
2230
|
const iconClass = event.status === 'success' ? 'success' :
|
|
@@ -1791,12 +2311,35 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1791
2311
|
switch (this.currentStatus) {
|
|
1792
2312
|
case 'idle': return 'Ready';
|
|
1793
2313
|
case 'pending': return 'Waiting for agent...';
|
|
1794
|
-
case 'fixing': return
|
|
2314
|
+
case 'fixing': return WORKING_PHRASES[this.phraseIndex % WORKING_PHRASES.length];
|
|
1795
2315
|
case 'success': return 'Done!';
|
|
1796
2316
|
case 'failed': return 'Failed';
|
|
1797
2317
|
default: return this.currentStatus;
|
|
1798
2318
|
}
|
|
1799
2319
|
}
|
|
2320
|
+
startPhraseRotation() {
|
|
2321
|
+
if (this.phraseInterval)
|
|
2322
|
+
return;
|
|
2323
|
+
this.phraseIndex = Math.floor(Math.random() * WORKING_PHRASES.length);
|
|
2324
|
+
this.phraseInterval = window.setInterval(() => {
|
|
2325
|
+
this.phraseIndex = (this.phraseIndex + 1) % WORKING_PHRASES.length;
|
|
2326
|
+
this.updateFooterText();
|
|
2327
|
+
}, 10000);
|
|
2328
|
+
}
|
|
2329
|
+
stopPhraseRotation() {
|
|
2330
|
+
if (this.phraseInterval) {
|
|
2331
|
+
window.clearInterval(this.phraseInterval);
|
|
2332
|
+
this.phraseInterval = null;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
updateFooterText() {
|
|
2336
|
+
if (!this.panel)
|
|
2337
|
+
return;
|
|
2338
|
+
const statusText = this.panel.querySelector('.status-text');
|
|
2339
|
+
if (statusText) {
|
|
2340
|
+
statusText.textContent = this.getStatusText();
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
1800
2343
|
hidePanel() {
|
|
1801
2344
|
if (this.panel) {
|
|
1802
2345
|
this.panel.remove();
|
|
@@ -1817,6 +2360,7 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1817
2360
|
const payload = {
|
|
1818
2361
|
interactionId: this.interactionId,
|
|
1819
2362
|
userNote: userNote.trim(),
|
|
2363
|
+
autoCommit: this.autoCommitEnabled,
|
|
1820
2364
|
...(snapshots.length === 1
|
|
1821
2365
|
? { snapshot: snapshots[0] }
|
|
1822
2366
|
: { snapshots }),
|
|
@@ -1877,6 +2421,65 @@ export class EyeglassInspector extends HTMLElement {
|
|
|
1877
2421
|
this.renderPanel();
|
|
1878
2422
|
}
|
|
1879
2423
|
}
|
|
2424
|
+
async submitFollowUp(userNote) {
|
|
2425
|
+
if (!userNote.trim())
|
|
2426
|
+
return;
|
|
2427
|
+
if (this.submittedSnapshots.length === 0)
|
|
2428
|
+
return;
|
|
2429
|
+
// Create new interaction ID for the follow-up
|
|
2430
|
+
this.interactionId = `eyeglass-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2431
|
+
this._userNote = userNote.trim();
|
|
2432
|
+
// Reuse the same snapshots from the previous request
|
|
2433
|
+
const snapshots = this.submittedSnapshots;
|
|
2434
|
+
const payload = {
|
|
2435
|
+
interactionId: this.interactionId,
|
|
2436
|
+
userNote: userNote.trim(),
|
|
2437
|
+
autoCommit: this.autoCommitEnabled,
|
|
2438
|
+
...(snapshots.length === 1
|
|
2439
|
+
? { snapshot: snapshots[0] }
|
|
2440
|
+
: { snapshots }),
|
|
2441
|
+
};
|
|
2442
|
+
// Build component name for history
|
|
2443
|
+
const componentNames = snapshots.map(s => s.framework.componentName || s.tagName);
|
|
2444
|
+
const historyComponentName = snapshots.length === 1
|
|
2445
|
+
? componentNames[0]
|
|
2446
|
+
: `${componentNames.length} elements`;
|
|
2447
|
+
// Add to history
|
|
2448
|
+
this.addToHistory({
|
|
2449
|
+
interactionId: this.interactionId,
|
|
2450
|
+
userNote: userNote.trim(),
|
|
2451
|
+
componentName: historyComponentName,
|
|
2452
|
+
filePath: snapshots[0]?.framework.filePath,
|
|
2453
|
+
status: 'pending',
|
|
2454
|
+
timestamp: Date.now(),
|
|
2455
|
+
});
|
|
2456
|
+
// Reset activity state for new request
|
|
2457
|
+
this.activityEvents = [];
|
|
2458
|
+
this.currentStatus = 'pending';
|
|
2459
|
+
this.renderPanel();
|
|
2460
|
+
try {
|
|
2461
|
+
const response = await fetch(`${BRIDGE_URL}/focus`, {
|
|
2462
|
+
method: 'POST',
|
|
2463
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2464
|
+
body: JSON.stringify(payload),
|
|
2465
|
+
});
|
|
2466
|
+
if (!response.ok) {
|
|
2467
|
+
throw new Error(`HTTP ${response.status}`);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
catch (err) {
|
|
2471
|
+
this.currentStatus = 'failed';
|
|
2472
|
+
this.updateHistoryStatus(this.interactionId, 'failed');
|
|
2473
|
+
this.activityEvents.push({
|
|
2474
|
+
type: 'status',
|
|
2475
|
+
interactionId: this.interactionId,
|
|
2476
|
+
status: 'failed',
|
|
2477
|
+
message: 'Failed to connect to bridge',
|
|
2478
|
+
timestamp: Date.now(),
|
|
2479
|
+
});
|
|
2480
|
+
this.renderPanel();
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
1880
2483
|
async submitAnswer(questionId, answerId, answerLabel) {
|
|
1881
2484
|
if (!this.interactionId)
|
|
1882
2485
|
return;
|