Skip to content

Commit b18fcc3

Browse files
committed
feat: added hirarcy based semantic mapping + AI extraction
1 parent 6089097 commit b18fcc3

File tree

17 files changed

+3939
-334
lines changed

17 files changed

+3939
-334
lines changed

extension/src/entrypoints/background.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
StoredCustomKeyEvent,
77
StoredEvent,
88
StoredRrwebEvent,
9+
StoredExtractionEvent,
910
} from "../lib/types";
1011
import {
1112
ClickStep,
@@ -15,6 +16,7 @@ import {
1516
ScrollStep,
1617
Step,
1718
Workflow,
19+
ExtractStep,
1820
} from "../lib/workflow-types";
1921
import {
2022
HttpEvent,
@@ -80,6 +82,8 @@ export default defineBackground(() => {
8082
return null; // Scroll steps will have null description like in the example
8183
case "key_press":
8284
return "Key press element";
85+
case "extract":
86+
return "Extract information with AI";
8387
default:
8488
return "Unknown action";
8589
}
@@ -95,6 +99,10 @@ export default defineBackground(() => {
9599
})
96100
.sort((a, b) => a.timestamp - b.timestamp); // Sort chronologically
97101

102+
console.log(`🔄 Processing ${allSteps.length} steps for workflow update`);
103+
const extractionSteps = allSteps.filter(s => (s as any).type === 'extract');
104+
console.log(`🤖 Found ${extractionSteps.length} extraction steps:`, extractionSteps);
105+
98106
// Convert steps to semantic format with proper descriptions
99107
const semanticSteps = allSteps.map((step, index) => {
100108
const semanticStep: any = {
@@ -111,10 +119,15 @@ export default defineBackground(() => {
111119
delete semanticStep.elementText;
112120
delete semanticStep.screenshot;
113121

114-
// Handle scroll steps specifically
122+
// Handle different step types specifically
115123
if (step.type === "scroll") {
116124
delete semanticStep.targetId;
117125
// Keep scrollX and scrollY for scroll steps
126+
} else if (step.type === "extract") {
127+
// For extraction steps, preserve extractionGoal and url
128+
// Keep: extractionGoal, url, type, description
129+
// Already removed: timestamp, tabId, screenshot (these are correct to remove)
130+
console.log(`🤖 Processing extraction step:`, semanticStep);
118131
}
119132

120133
// Convert targetText to target_text for semantic workflow compatibility
@@ -129,6 +142,9 @@ export default defineBackground(() => {
129142
return semanticStep;
130143
});
131144

145+
const semanticExtractionSteps = semanticSteps.filter(s => s.type === 'extract');
146+
console.log(`✅ Final semantic steps include ${semanticExtractionSteps.length} extraction steps:`, semanticExtractionSteps);
147+
132148
// Create the workflowData object for the Python server (semantic format)
133149
const semanticWorkflowData: Workflow = {
134150
workflow_analysis: "Semantic version of recorded workflow. Uses visible text to identify elements instead of CSS selectors for improved reliability.",
@@ -477,6 +493,24 @@ export default defineBackground(() => {
477493
break;
478494
}
479495

496+
case "EXTRACTION_STEP": {
497+
const extractEvent = event as any; // Type assertion for extraction event
498+
if (extractEvent.url && extractEvent.extractionGoal) {
499+
const step: ExtractStep = {
500+
type: "extract",
501+
timestamp: extractEvent.timestamp,
502+
tabId: extractEvent.tabId,
503+
url: extractEvent.url,
504+
extractionGoal: extractEvent.extractionGoal,
505+
screenshot: extractEvent.screenshot,
506+
};
507+
steps.push(step);
508+
} else {
509+
console.warn("Skipping incomplete EXTRACTION_STEP:", extractEvent);
510+
}
511+
break;
512+
}
513+
480514
// Add cases for other StoredEvent types to Step types if needed
481515
// e.g., CUSTOM_SELECT_EVENT -> SelectStep
482516
// e.g., CUSTOM_TAB_CREATED -> TabCreatedStep
@@ -666,6 +700,83 @@ export default defineBackground(() => {
666700
}
667701
sendResponse({ status: "stopped" }); // Send simple confirmation
668702
}
703+
// --- Add Extraction Step from Sidepanel ---
704+
else if (message.type === "ADD_EXTRACTION_STEP") {
705+
console.log("🤖 Received ADD_EXTRACTION_STEP request:", message.payload);
706+
707+
if (!isRecordingEnabled) {
708+
console.error("❌ Recording is not enabled");
709+
sendResponse({ status: "error", message: "Recording is not active" });
710+
return false;
711+
}
712+
713+
try {
714+
// For sidepanel messages, we need to get the active tab
715+
// Since this is from sidepanel, sender.tab will be undefined
716+
// Let's use a direct approach with chrome.tabs.query but handle it synchronously
717+
718+
isAsync = true;
719+
720+
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
721+
try {
722+
console.log("📋 Active tabs found:", tabs?.length || 0);
723+
724+
if (chrome.runtime.lastError) {
725+
console.error("❌ Chrome tabs query error:", chrome.runtime.lastError);
726+
sendResponse({ status: "error", message: "Chrome tabs query failed" });
727+
return;
728+
}
729+
730+
if (!tabs || tabs.length === 0 || !tabs[0]?.id) {
731+
console.error("❌ No active tab found");
732+
sendResponse({ status: "error", message: "No active tab found" });
733+
return;
734+
}
735+
736+
const tabId = tabs[0].id;
737+
const tabUrl = tabs[0].url || "";
738+
739+
console.log("✅ Using tab ID:", tabId, "URL:", tabUrl);
740+
741+
const extractionStep: StoredExtractionEvent = {
742+
timestamp: message.payload.timestamp,
743+
tabId: tabId,
744+
url: tabUrl,
745+
extractionGoal: message.payload.extractionGoal,
746+
messageType: "EXTRACTION_STEP",
747+
};
748+
749+
console.log("📝 Creating extraction step:", extractionStep);
750+
751+
if (!sessionLogs[tabId]) {
752+
console.log("🆕 Initializing sessionLogs for tab:", tabId);
753+
sessionLogs[tabId] = [];
754+
}
755+
756+
sessionLogs[tabId].push(extractionStep);
757+
console.log("✅ Added extraction step to sessionLogs. Total events for tab:", sessionLogs[tabId].length);
758+
759+
// Broadcast update (don't await to avoid blocking)
760+
broadcastWorkflowDataUpdate();
761+
console.log("✅ Broadcasted workflow update");
762+
763+
// Send success response
764+
sendResponse({ status: "added" });
765+
766+
} catch (error) {
767+
console.error("❌ Error in tabs.query callback:", error);
768+
sendResponse({ status: "error", message: `Callback error: ${error}` });
769+
}
770+
});
771+
772+
return true; // Keep message channel open
773+
774+
} catch (error) {
775+
console.error("❌ Error setting up extraction step:", error);
776+
sendResponse({ status: "error", message: `Setup error: ${error}` });
777+
return false;
778+
}
779+
}
669780
// --- Status Request from Content Script ---
670781
else if (message.type === "REQUEST_RECORDING_STATUS" && sender.tab?.id) {
671782
console.log(

extension/src/entrypoints/sidepanel/components/event-viewer.tsx

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,67 @@ const getScreenshot = (step: Step): string | undefined => {
1717
return undefined;
1818
};
1919

20+
// Helper function to format step details based on type
21+
const formatStepDetails = (step: any): string => {
22+
switch (step.type) {
23+
case "navigation":
24+
return step.url;
25+
case "click":
26+
return step.targetText || step.elementText || step.cssSelector || "Click action";
27+
case "input":
28+
const value = step.value || "";
29+
const target = step.targetText || step.cssSelector || "Input field";
30+
return `${target}: "${value}"`;
31+
case "key_press":
32+
return `Key: ${step.key}`;
33+
case "scroll":
34+
return `(${step.scrollX}, ${step.scrollY})`;
35+
case "extract":
36+
return step.extractionGoal || "AI Extraction";
37+
default:
38+
return "Unknown action";
39+
}
40+
};
41+
42+
// Helper function to get step icon based on type
43+
const getStepIcon = (step: any): string => {
44+
switch (step.type) {
45+
case "navigation":
46+
return "🔗";
47+
case "click":
48+
return "👆";
49+
case "input":
50+
return "✏️";
51+
case "key_press":
52+
return "⌨️";
53+
case "scroll":
54+
return "📜";
55+
case "extract":
56+
return "🤖";
57+
default:
58+
return "❓";
59+
}
60+
};
61+
62+
// Helper function to get background color based on step type
63+
const getStepBgColor = (step: any, isSelected: boolean): string => {
64+
if (isSelected) {
65+
switch (step.type) {
66+
case "extract":
67+
return "bg-blue-100 border-blue-300";
68+
default:
69+
return "bg-blue-50 border-blue-200";
70+
}
71+
} else {
72+
switch (step.type) {
73+
case "extract":
74+
return "bg-blue-25 border-blue-100 hover:bg-blue-50";
75+
default:
76+
return "bg-white border-gray-200 hover:bg-gray-50";
77+
}
78+
}
79+
};
80+
2081
// Component to render a single step as a card
2182
const StepCard: React.FC<{
2283
step: Step;
@@ -209,6 +270,17 @@ const StepCard: React.FC<{
209270
);
210271
break;
211272
}
273+
case "extract": {
274+
const s = step as any; // ExtractStep
275+
specificInfo = (
276+
<>
277+
<p>
278+
<strong>Extraction Goal:</strong> {s.extractionGoal}
279+
</p>
280+
</>
281+
);
282+
break;
283+
}
212284
default:
213285
specificInfo = (
214286
<p>Details not available for type: {(step as any).type}</p>
@@ -223,19 +295,20 @@ const StepCard: React.FC<{
223295
);
224296
};
225297

226-
return (
227-
<div
228-
id={`event-item-${index}`} // Keep ID for potential scrolling
229-
onClick={onSelect}
230-
className={`
231-
border rounded-lg mb-3 overflow-hidden cursor-pointer transition-all duration-150 ease-in-out
232-
${
233-
isSelected
234-
? "border-primary shadow-md scale-[1.01]"
235-
: "border-border hover:border-muted-foreground/50 hover:shadow-sm"
236-
}
237-
`}
238-
>
298+
return (
299+
<div
300+
id={`event-item-${index}`} // Keep ID for potential scrolling
301+
onClick={onSelect}
302+
className={`
303+
border rounded-lg mb-3 overflow-hidden cursor-pointer transition-all duration-150 ease-in-out
304+
${getStepBgColor(step, isSelected)}
305+
${
306+
isSelected
307+
? "shadow-md scale-[1.01]"
308+
: "hover:shadow-sm"
309+
}
310+
`}
311+
>
239312
{/* Card Content using Flexbox */}
240313
<div className="flex items-start p-3 space-x-3">
241314
{/* Left side: Summary and Details */}

0 commit comments

Comments
 (0)