diff --git a/extension/app/background.ts b/extension/app/background.ts
index ad0507650efc..b6a5b67a1f08 100644
--- a/extension/app/background.ts
+++ b/extension/app/background.ts
@@ -15,16 +15,17 @@ import type {
GetActiveTabBackgroundResponse,
InputBarStatusMessage,
} from "./src/lib/messages";
-import { sendAttachSelection as sendAttachSelection } from "./src/lib/messages";
import { generatePKCE } from "./src/lib/utils";
const log = console.error;
const state: {
+ port: chrome.runtime.Port | undefined;
extensionReady: boolean;
inputBarReady: boolean;
lastHandler: (() => void) | undefined;
} = {
+ port: undefined,
extensionReady: false,
inputBarReady: false,
lastHandler: undefined,
@@ -46,6 +47,11 @@ chrome.runtime.onUpdateAvailable.addListener(async (details) => {
*/
chrome.runtime.onInstalled.addListener(() => {
void chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
+ chrome.contextMenus.create({
+ id: "ask_dust",
+ title: "Ask @dust to summarize this page",
+ contexts: ["all"],
+ });
chrome.contextMenus.create({
id: "add_tab_content",
title: "Add tab content to conversation",
@@ -67,10 +73,12 @@ chrome.runtime.onInstalled.addListener(() => {
chrome.runtime.onConnect.addListener((port) => {
if (port.name === "sidepanel-connection") {
console.log("Sidepanel is there");
+ state.port = port;
state.extensionReady = true;
port.onDisconnect.addListener(() => {
// This fires when sidepanel closes
console.log("Sidepanel was closed");
+ state.port = undefined;
state.extensionReady = false;
state.inputBarReady = false;
state.lastHandler = undefined;
@@ -80,27 +88,53 @@ chrome.runtime.onConnect.addListener((port) => {
const getActionHandler = (menuItemId: string | number) => {
switch (menuItemId) {
+ case "ask_dust":
+ return () => {
+ if (state.port) {
+ const params = JSON.stringify({
+ includeContent: true,
+ includeScreenshot: false,
+ text: ":mention[dust]{sId=dust} summarize this page.",
+ configurationId: "dust",
+ });
+ state.port.postMessage({
+ type: "ROUTE_CHANGE",
+ pathname: "/run",
+ search: `?${params}`,
+ });
+ }
+ };
case "add_tab_content":
- return () =>
- void sendAttachSelection({
- includeContent: true,
- includeScreenshot: false,
- });
- break;
+ return () => {
+ if (state.port) {
+ state.port.postMessage({
+ type: "ATTACH_TAB",
+ includeContent: true,
+ includeScreenshot: false,
+ });
+ }
+ };
case "add_tab_screenshot":
- return () =>
- void sendAttachSelection({
- includeContent: false,
- includeScreenshot: true,
- });
- break;
+ return () => {
+ if (state.port) {
+ state.port.postMessage({
+ type: "ATTACH_TAB",
+ includeContent: false,
+ includeScreenshot: true,
+ });
+ }
+ };
case "add_selection":
- return () =>
- void sendAttachSelection({
- includeContent: true,
- includeScreenshot: false,
- includeSelectionOnly: true,
- });
+ return () => {
+ if (state.port) {
+ state.port.postMessage({
+ type: "ATTACH_TAB",
+ includeContent: true,
+ includeScreenshot: false,
+ includeSelectionOnly: true,
+ });
+ }
+ };
}
};
@@ -116,10 +150,8 @@ chrome.contextMenus.onClicked.addListener(async (event, tab) => {
void chrome.sidePanel.open({
windowId: tab.windowId,
});
- } else if (state.inputBarReady) {
- handler();
} else {
- // Extension is loaded but the input bar is not visible - do nothing.
+ await handler();
}
});
diff --git a/extension/app/main.tsx b/extension/app/main.tsx
index fcdea4a8c0a4..5bf3187f168d 100644
--- a/extension/app/main.tsx
+++ b/extension/app/main.tsx
@@ -9,6 +9,7 @@ import "./src/css/custom.css";
import { Notification } from "@dust-tt/sparkle";
import { AuthProvider } from "@extension/components/auth/AuthProvider";
+import { PortProvider } from "@extension/components/PortContext";
import { routes } from "@extension/pages/routes";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
@@ -16,13 +17,14 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter(routes);
const App = () => {
- chrome.runtime.connect({ name: "sidepanel-connection" });
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
};
const rootElement = document.getElementById("root");
diff --git a/extension/app/src/components/PortContext.tsx b/extension/app/src/components/PortContext.tsx
new file mode 100644
index 000000000000..df4e6930685c
--- /dev/null
+++ b/extension/app/src/components/PortContext.tsx
@@ -0,0 +1,19 @@
+import { createContext, useEffect, useState } from "react";
+
+export const PortContext = createContext(null);
+
+export const PortProvider = ({ children }: { children: React.ReactNode }) => {
+ const [port, setPort] = useState(null);
+
+ useEffect(() => {
+ const port = chrome.runtime.connect({ name: "sidepanel-connection" });
+
+ setPort(port);
+
+ return () => {
+ port.disconnect();
+ };
+ }, []);
+
+ return {children};
+};
diff --git a/extension/app/src/components/auth/ProtectedRoute.tsx b/extension/app/src/components/auth/ProtectedRoute.tsx
index 9c53ed44f28e..fd30d1af2522 100644
--- a/extension/app/src/components/auth/ProtectedRoute.tsx
+++ b/extension/app/src/components/auth/ProtectedRoute.tsx
@@ -6,10 +6,12 @@ import {
Spinner,
} from "@dust-tt/sparkle";
import { useAuth } from "@extension/components/auth/AuthProvider";
+import { PortContext } from "@extension/components/PortContext";
+import type { RouteChangeMesssage } from "@extension/lib/messages";
import type { StoredUser } from "@extension/lib/storage";
import { getPendingUpdate } from "@extension/lib/storage";
import type { ReactNode } from "react";
-import { useEffect, useState } from "react";
+import { useContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
type ProtectedRouteProps = {
@@ -35,6 +37,23 @@ export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const navigate = useNavigate();
const [isLatestVersion, setIsLatestVersion] = useState(true);
+ const port = useContext(PortContext);
+ useEffect(() => {
+ if (port) {
+ const listener = (message: RouteChangeMesssage) => {
+ const { type } = message;
+ if (type === "ROUTE_CHANGE") {
+ navigate({ pathname: message.pathname, search: message.search });
+ return false;
+ }
+ };
+ port.onMessage.addListener(listener);
+ return () => {
+ port.onMessage.removeListener(listener);
+ };
+ }
+ }, [port, navigate]);
+
useEffect(() => {
if (!isAuthenticated || !isUserSetup || !user || !workspace) {
navigate("/login");
diff --git a/extension/app/src/components/input_bar/InputBar.tsx b/extension/app/src/components/input_bar/InputBar.tsx
index af189033c7cc..3f7bd5ab6762 100644
--- a/extension/app/src/components/input_bar/InputBar.tsx
+++ b/extension/app/src/components/input_bar/InputBar.tsx
@@ -13,6 +13,7 @@ import { InputBarCitations } from "@extension/components/input_bar/InputBarCitat
import type { InputBarContainerProps } from "@extension/components/input_bar/InputBarContainer";
import { InputBarContainer } from "@extension/components/input_bar/InputBarContainer";
import { InputBarContext } from "@extension/components/input_bar/InputBarContext";
+import { PortContext } from "@extension/components/PortContext";
import { useFileUploaderService } from "@extension/hooks/useFileUploaderService";
import { useDustAPI } from "@extension/lib/dust_api";
import type { AttachSelectionMessage } from "@extension/lib/messages";
@@ -58,21 +59,24 @@ export function AssistantInputBar({
owner,
});
+ const port = useContext(PortContext);
useEffect(() => {
- void sendInputBarStatus(true);
- const listener = (message: AttachSelectionMessage) => {
- const { type, ...options } = message;
- if (type === "ATTACH_TAB") {
- // Handle message
- void fileUploaderService.uploadContentTab(options);
- }
- };
- chrome.runtime.onMessage.addListener(listener);
- return () => {
- void sendInputBarStatus(false);
- chrome.runtime.onMessage.removeListener(listener);
- };
- });
+ if (port) {
+ void sendInputBarStatus(true);
+ const listener = async (message: AttachSelectionMessage) => {
+ const { type } = message;
+ if (type === "ATTACH_TAB") {
+ // Handle message
+ void fileUploaderService.uploadContentTab(message);
+ }
+ };
+ port.onMessage.addListener(listener);
+ return () => {
+ void sendInputBarStatus(false);
+ port.onMessage.removeListener(listener);
+ };
+ }
+ }, []);
const { droppedFiles, setDroppedFiles } = useFileDrop();
diff --git a/extension/app/src/lib/messages.ts b/extension/app/src/lib/messages.ts
index fe3db40e8cf1..06ed4d8eae3c 100644
--- a/extension/app/src/lib/messages.ts
+++ b/extension/app/src/lib/messages.ts
@@ -41,6 +41,12 @@ export type InputBarStatusMessage = {
available: boolean;
};
+export type RouteChangeMesssage = {
+ type: "ROUTE_CHANGE";
+ pathname: string;
+ search: string;
+};
+
const sendMessage = (message: T): Promise => {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(message, (response: U | undefined) => {
@@ -147,6 +153,15 @@ export const sendGetActiveTabMessage = (params: GetActiveTabOptions) => {
});
};
+export const sendInputBarStatus = (available: boolean) => {
+ return sendMessage({
+ type: "INPUT_BAR_STATUS",
+ available,
+ });
+};
+
+// Messages from background script to content script
+
export const sendAttachSelection = (
opts: GetActiveTabOptions = { includeContent: true, includeScreenshot: false }
) => {
@@ -155,10 +170,3 @@ export const sendAttachSelection = (
...opts,
});
};
-
-export const sendInputBarStatus = (available: boolean) => {
- return sendMessage({
- type: "INPUT_BAR_STATUS",
- available,
- });
-};
diff --git a/extension/app/src/pages/RunPage.tsx b/extension/app/src/pages/RunPage.tsx
new file mode 100644
index 000000000000..14c838a6a6ae
--- /dev/null
+++ b/extension/app/src/pages/RunPage.tsx
@@ -0,0 +1,60 @@
+import type { LightWorkspaceType } from "@dust-tt/client";
+import { Spinner } from "@dust-tt/sparkle";
+import { useFileUploaderService } from "@extension/hooks/useFileUploaderService";
+import { postConversation } from "@extension/lib/conversation";
+import { useDustAPI } from "@extension/lib/dust_api";
+import { useEffect } from "react";
+import { useLocation, useNavigate } from "react-router";
+
+export const RunPage = ({ workspace }: { workspace: LightWorkspaceType }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const dustAPI = useDustAPI();
+
+ const fileUploaderService = useFileUploaderService({
+ owner: workspace,
+ });
+
+ useEffect(() => {
+ const run = async () => {
+ const params = JSON.parse(decodeURI(location.search.substr(1)));
+
+ const files = await fileUploaderService.uploadContentTab({
+ includeContent: params.includeContent,
+ includeScreenshot: params.includeScreenshot,
+ includeSelectionOnly: params.includeSelectionOnly,
+ updateBlobs: false,
+ });
+
+ const conversationRes = await postConversation({
+ dustAPI,
+ messageData: {
+ input: params.text,
+ mentions: [{ configurationId: params.configurationId }],
+ },
+ contentFragments: files
+ ? files.map((cf) => ({
+ title: cf.filename,
+ fileId: cf.fileId || "",
+ url: cf.publicUrl,
+ }))
+ : [],
+ });
+
+ fileUploaderService.resetUpload();
+
+ if (conversationRes.isOk()) {
+ navigate(`/conversations/${conversationRes.value.sId}`);
+ } else {
+ navigate("/");
+ }
+ };
+
+ void run();
+ }, []);
+ return (
+
+
+
+ );
+};
diff --git a/extension/app/src/pages/routes.tsx b/extension/app/src/pages/routes.tsx
index 7e7ccca72614..6e022b7ae450 100644
--- a/extension/app/src/pages/routes.tsx
+++ b/extension/app/src/pages/routes.tsx
@@ -3,6 +3,7 @@ import { ConversationPage } from "@extension/pages/ConversationPage";
import { ConversationsPage } from "@extension/pages/ConversationsPage";
import { LoginPage } from "@extension/pages/LoginPage";
import { MainPage } from "@extension/pages/MainPage";
+import { RunPage } from "@extension/pages/RunPage";
export const routes = [
{
@@ -51,4 +52,12 @@ export const routes = [
),
},
+ {
+ path: "/run",
+ element: (
+
+ {({ workspace }) => }
+
+ ),
+ },
];