Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions client/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@
"tabs"
],
"oauth2": {
"client_id": "581875153522-44qhjt4693geitf1cis0bf91rkf7dmja.apps.googleusercontent.com",
"scopes": ["profile", "email"]
"client_id": "581875153522-i2is24sdjrq7ekvohqfqiljj2gthhdrd.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive.file"
]
},

"host_permissions": ["http://*/*", "https://*/*"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self';"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/page/home/NotificationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export interface Item {
name: string;
description: string;
icon: string;
color: string;
color?: string;
time: string;
}
const NotificationItem = ({ name, description, icon, color, time }: Item) => {
Expand Down
3 changes: 1 addition & 2 deletions client/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export const AUTOMATION_ERROR_NOTIFICATION: Item = {
description: "Something went wrong during automation.",
time: "now",
icon: "⚠️",
color: "#ef4444", // red-500
};
export const INITIAL_NOTIFICATION_MESSAGE = "Waking up the bots...";
export const WAITING_FOR_AI_MESSAGE = "AI is deep in thought...";
Expand Down Expand Up @@ -99,7 +98,7 @@ export const INITIAL_NOTIFICATIONS: Omit<any, "time">[] = [
export const SUCCESS_NOTIFICATION_BASE: Omit<any, "time" | "description"> = {
name: "SUCCESS",
icon: "✅",
color: "#10B981",
// color: "#10B981",
};

export const ERROR_NOTIFICATION_BASE: Omit<any, "time" | "description"> = {
Expand Down
128 changes: 57 additions & 71 deletions client/src/hooks/useAutomationRunner.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// ... existing code ...
import { AuthContext } from "@/contexts/AuthContext";
import { useContext, useState } from "react";
import { useAiFormFiller } from "./useAiFormFiller";
// import { useAutomationNotifications } from "./useAutomationNotifications"; // Removed
import { useFormInjector } from "./useFormInjector";
import { useFormStructurer } from "./useFormStructurer";
import { useHtmlExtractor } from "./useHtmlExtractor";
import { Item as NotificationItemProps } from "@/components/page/home/NotificationItem"; // Added for type safety
import { Item as NotificationItemProps } from "@/components/page/home/NotificationItem";
import { useSheetDetector } from "./useSheetDetector";
import { useSheetAutomation } from "./useSheetAutomation";
import { useSheetMetadata } from "./useSheetMetadata";
import { useGoogleAuth } from "./useGoogleAuth";

// Define the props for notification handlers
interface AutomationRunnerProps {
Expand All @@ -16,7 +18,7 @@ interface AutomationRunnerProps {
onAuthError: () => void;
onSuccess: () => void;
onError: (message?: string) => void;
onAddNotification: (message: string, type: 'add' | 'replace') => void; // Updated signature
onAddNotification: (message: string, type: "add" | "replace") => void; // Updated signature
onReplaceNotification: (notification: NotificationItemProps) => void;
onClearNotifications: () => void;
}
Expand Down Expand Up @@ -50,93 +52,77 @@ export const useAutomationRunner = (props: AutomationRunnerProps) => {

const auth = useContext(AuthContext);
const [isRunning, setIsRunning] = useState(false);

// Removed internal useAutomationNotifications call

const { extractHtml } = useHtmlExtractor();
const { structureForm } = useFormStructurer();
const { sendToAI } = useAiFormFiller();
const { injectFields } = useFormInjector();
const { isGoogleSheet } = useSheetDetector();
const { classifyIntent, executeAutomation } = useSheetAutomation();
const { extractMetadata } = useSheetMetadata();
const { authenticateWithGoogle } = useGoogleAuth();

const runAutomation = async (userInput: string) => {
// Input validation is now handled in Home.tsx before calling runAutomation
// Auth check
if (!auth || !auth.token) {
console.log("Authentication Error: User not logged in or token expired.");
onAuthError(); // Call the handler from Home.tsx
onAuthError();
return;
}

setIsRunning(true);
// Clear any previous notifications and schedule initial ones via Home.tsx callbacks
// onClearNotifications(); // Home.tsx's handleAutomationClick already clears.
// The FORM_FILLING_NOTIFICATIONS constant is defined in Home.tsx and passed to onScheduleInitialNotifications there.
// This call is now made from Home.tsx before runAutomation, or as the first step here if preferred.
// For now, assuming Home.tsx calls its `scheduleNotifications(INITIAL_AUTOMATION_NOTIFICATIONS)`
// which in turn calls the `onScheduleInitialNotifications` passed to this hook.
// To be very explicit as per user request:
// onScheduleInitialNotifications should be called by Home.tsx, which then calls the function passed here.
// If Home.tsx passes its own `scheduleNotifications` as `onScheduleInitialNotifications`, that's fine.
// The key is that the initial sequence starts immediately.
// Let's assume Home.tsx's `useAutomationRunner` call looks like:
// useAutomationRunner({ onScheduleInitialNotifications: () => scheduleNotifications(INITIAL_AUTOMATION_NOTIFICATIONS), ... })
// So, when runAutomation is called, Home.tsx has already (or will immediately) call its local scheduleNotifications.
// To ensure it's the *very first* thing as per user request, Home.tsx should call its local `scheduleNotifications`
// *before* calling `runAutomation(formDataInput)`.
// The `onScheduleInitialNotifications` prop is more for the hook to trigger it if it were managing the constants.
// Given Home.tsx now owns the constants, it's more direct for Home.tsx to initiate this.

// However, to strictly follow the user's checklist for the hook:
// If `FORM_FILLING_NOTIFICATIONS` were defined here or passed in:
// onScheduleInitialNotifications(FORM_FILLING_NOTIFICATIONS_FROM_SOMEWHERE);
// Since Home.tsx defines them, it's cleaner for Home.tsx to call its own `scheduleNotifications`
// and pass that function as `onScheduleInitialNotifications`.
// The current setup in the previous Home.tsx diff already does this effectively.

try {
console.log("Starting automation...");

// 1. HTML Extraction
console.log("Step 1: Extracting HTML...");
// Notification for this step is part of the initial scheduled sequence from Home.tsx
const htmlContent = await extractHtml();
console.log("HTML extracted successfully.");

// 2. Structuring Form
console.log("Step 2: Structuring form data...");
// Notification for this step is part of the initial scheduled sequence from Home.tsx
const structuredForm = await structureForm(htmlContent);
console.log("Form data structured successfully.");

// 3. Sending to AI
console.log("Step 3: Sending data to AI...");
// Notification for this step is part of the initial scheduled sequence from Home.tsx
const aiData = await sendToAI(userInput, structuredForm);
console.log("AI response received.");
onClearNotifications();
// Pass description as message, and type as 'replace' or 'add'
// Assuming AI_RESPONSE_RECEIVED_NOTIFICATION should replace the last one (e.g., "Waiting for AI")
onAddNotification(AI_RESPONSE_RECEIVED_NOTIFICATION.description, 'replace');

console.log("Step 4: Injecting form values...");
// Assuming INJECTING_FORM_VALUES_NOTIFICATION should be added as a new one
onAddNotification(INJECTING_FORM_VALUES_NOTIFICATION.description, 'add');
await injectFields(aiData);
console.log("Form values injected successfully.");

onSuccess();
if (isGoogleSheet) {
// Google Sheets authentication
try {
const googleToken = await authenticateWithGoogle();
console.log("Successfully authenticated with Google", googleToken);

// Google Sheets flow
const intentResult = await classifyIntent(userInput);
if (intentResult?.intent === "unknown") {
throw new Error("Unable to determine automation intent");
}

const sheetMetadata = await extractMetadata();
if (!sheetMetadata) {
throw new Error("Failed to extract sheet metadata");
}

await executeAutomation(intentResult?.intent || "unknown", userInput);
onSuccess();
} catch (googleError) {
console.error("Google authentication error:", googleError);
onError("Failed to authenticate with Google Sheets");
return;
}
} else {
// Existing form automation flow
console.log("Starting form automation...");
const htmlContent = await extractHtml();
const structuredForm = await structureForm(htmlContent);
const aiData = await sendToAI(userInput, structuredForm);

onClearNotifications();
onAddNotification(
AI_RESPONSE_RECEIVED_NOTIFICATION.description,
"replace"
);
onAddNotification(
INJECTING_FORM_VALUES_NOTIFICATION.description,
"add"
);

await injectFields(aiData);
onSuccess();
}
} catch (error: any) {
console.error("Automation error:", error);
const errorMessage =
error.message || "An unexpected error occurred during automation.";
onError(errorMessage); // Call error handler from Home.tsx
onError(errorMessage);
} finally {
setIsRunning(false);
}
};

return {
runAutomation,
isRunning,
};
return { runAutomation, isRunning };
};
40 changes: 40 additions & 0 deletions client/src/hooks/useGoogleAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useCallback, useState } from 'react';

export const useGoogleAuth = () => {
const [token, setToken] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);

const authenticateWithGoogle = useCallback((): Promise<string> => {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: true }, (token) => {
if (chrome.runtime.lastError || !token) {
const error = chrome.runtime.lastError?.message || 'Authentication failed';
console.error('OAuth error:', error);
setError(error);
reject(error);
} else {
console.log('Google OAuth token received');
setToken(token);
resolve(token);
}
});
});
}, []);

const clearToken = useCallback(() => {
if (token) {
chrome.identity.removeCachedAuthToken({ token }, () => {
setToken(null);
setError(null);
});
}
}, [token]);

return {
token,
error,
authenticateWithGoogle,
clearToken,
isAuthenticated: !!token
};
};
93 changes: 93 additions & 0 deletions client/src/hooks/useSheetAutomation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState } from "react";
import { useSheetMetadata } from "./useSheetMetadata";
import { sheetsService } from "../lib/services/sheets";
import { IntentClassification } from "../types/sheets";
import api from "@/lib/api";

export const useSheetAutomation = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { metadata, extractMetadata } = useSheetMetadata();

const classifyIntent = async (
prompt: string
): Promise<IntentClassification | null> => {
setLoading(true);
try {
const response = await api.post("/sheets/classify", { prompt });
return response.data;
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to classify intent"
);
return null;
} finally {
setLoading(false);
}
};

const executeAutomation = async (
intent: string,
prompt: string
): Promise<boolean> => {
setLoading(true);
try {
const sheetMetadata = await extractMetadata();
if (!sheetMetadata) throw new Error("Failed to extract sheet metadata");

const response = await api.post("/sheets/automate", {
intent,
prompt,
sheetMetadata,
});

const result = response.data;

// Execute the appropriate action based on intent
switch (intent) {
case "data_entry":
await sheetsService.insertData(sheetMetadata, result.data);
break;
case "chart_generation":
await sheetsService.insertChart(sheetMetadata, result.chartConfig);
break;
case "sheet_modification": {
const requests = result.modifications.map((mod: any) => ({
updateCells: {
range: {
sheetId: sheetMetadata.sheetId,
startRowIndex: mod.range.startRow - 1,
endRowIndex: mod.range.endRow,
startColumnIndex: mod.range.startColumn.charCodeAt(0) - 65,
endColumnIndex: mod.range.endColumn.charCodeAt(0) - 64,
},
rows: mod.values.map((row: any[]) => ({
values: row.map((cell: any) => ({
userEnteredValue: { stringValue: cell.toString() },
})),
})),
fields: "userEnteredValue",
},
}));

await (window as any).gapi.client.sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetMetadata.spreadsheetId,
resource: { requests },
});
break;
}
}

return true;
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to execute automation"
);
return false;
} finally {
setLoading(false);
}
};

return { classifyIntent, executeAutomation, loading, error, metadata };
};
43 changes: 43 additions & 0 deletions client/src/hooks/useSheetDetector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from "react";

export const useSheetDetector = () => {
const [isGoogleSheet, setIsGoogleSheet] = useState(false);

useEffect(() => {
const checkIfGoogleSheet = () => {
if (chrome && chrome.tabs) {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
const title = currentTab?.title || "";
setIsGoogleSheet(title.includes("Google Sheets"));
});
}
};

checkIfGoogleSheet();

// Listen for tab updates
const handleTabUpdate = (
_tabId: number,
changeInfo: chrome.tabs.TabChangeInfo,
tab: chrome.tabs.Tab
) => {
if (tab.active && changeInfo.title) {
setIsGoogleSheet(changeInfo.title.includes("Google Sheets"));
}
};

if (chrome && chrome.tabs && chrome.tabs.onUpdated) {
chrome.tabs.onUpdated.addListener(handleTabUpdate);
}

// Cleanup listener
return () => {
if (chrome && chrome.tabs && chrome.tabs.onUpdated) {
chrome.tabs.onUpdated.removeListener(handleTabUpdate);
}
};
}, []);

return { isGoogleSheet };
};
Loading