diff --git a/Dockerfile b/Dockerfile
index f82bdb379..23991c497 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -39,6 +39,10 @@ ENV NEXT_PUBLIC_GA4_MEASUREMENT_ID=${NEXT_PUBLIC_GA4_MEASUREMENT_ID}
ARG NEXT_PUBLIC_POSTHOG_KEY
ENV NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
+# Optional argument to configure Google Drive Picker at build time (can reuse AUTH_GOOGLE_ID value)
+ARG NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID
+ENV NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID=${NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID}
+
# Copy development deps and source
COPY --from=deps /app/node_modules ./node_modules
COPY . .
diff --git a/docs/config-feature-google-drive.md b/docs/config-feature-google-drive.md
new file mode 100644
index 000000000..29f295ffa
--- /dev/null
+++ b/docs/config-feature-google-drive.md
@@ -0,0 +1,56 @@
+# Google Drive Integration
+
+Attach files from Google Drive directly in the chat composer.
+
+## Setup
+
+### 1. Enable APIs
+
+In [Google Cloud Console](https://console.cloud.google.com/):
+
+1. Go to **APIs & Services > Library**
+2. Enable **Google Drive API** and **Google Picker API**
+
+### 2. Configure OAuth
+
+1. Go to **APIs & Services > OAuth consent screen**
+2. Create consent screen (External or Internal)
+3. Add scope: `https://www.googleapis.com/auth/drive.file`
+4. Add test users if in testing mode
+
+### 3. Create Credentials
+
+1. Go to **APIs & Services > Credentials**
+2. Create **OAuth client ID** (Web application)
+3. Add JavaScript origins:
+ - `http://localhost:3000` (dev)
+ - `https://your-domain.com` (prod)
+
+### 4. Set Environment Variable
+
+```bash
+NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID=your-client-id.apps.googleusercontent.com
+```
+
+## Usage
+
+- Click **Drive** button in attachment menu
+- Or press **Ctrl + Shift + G**
+
+## Supported Files
+
+| Type | Export Format |
+|-----------------|---------------------|
+| Regular files | Downloaded directly |
+| Google Docs | Markdown (.md) |
+| Google Sheets | CSV (.csv) |
+| Google Slides | PDF (.pdf) |
+| Google Drawings | SVG (.svg) |
+
+## Troubleshooting
+
+**Picker won't open**: Check `NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID` is set and APIs are enabled.
+
+**OAuth errors**: Verify your domain is in authorized JavaScript origins. Add yourself as test user if app is in testing mode.
+
+**Download fails**: Check file permissions and that Drive API is enabled.
diff --git a/docs/environment-variables.md b/docs/environment-variables.md
index 8151ded40..88ffc256e 100644
--- a/docs/environment-variables.md
+++ b/docs/environment-variables.md
@@ -66,8 +66,9 @@ HTTP_BASIC_AUTH_PASSWORD=
# Frontend variables
NEXT_PUBLIC_MOTD=
NEXT_PUBLIC_GA4_MEASUREMENT_ID=
-NEXT_PUBLIC_POSTHOG_KEY=
+NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID=
NEXT_PUBLIC_PLANTUML_SERVER_URL=
+NEXT_PUBLIC_POSTHOG_KEY=
```
## Backend Variables
@@ -155,8 +156,9 @@ The value of these variables are passed to the frontend (Web UI) - make sure the
| `NEXT_PUBLIC_DEBUG_BREAKS` | (optional, development) When set to 'true', enables automatic debugger breaks on DEV/error/critical logs in development builds |
| `NEXT_PUBLIC_MOTD` | Message of the Day - displays a dismissible banner at the top of the app (see [customizations](customizations.md) for the template variables). Example: 🔔 Welcome to our deployment! Version {{app_build_pkgver}} built on {{app_build_time}}. |
| `NEXT_PUBLIC_GA4_MEASUREMENT_ID` | (optional) The measurement ID for Google Analytics 4. (see [deploy-analytics](deploy-analytics.md)) |
-| `NEXT_PUBLIC_POSTHOG_KEY` | (optional) Key for PostHog analytics. (see [deploy-analytics](deploy-analytics.md)) |
+| `NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID` | (optional) Google OAuth Client ID for Drive Picker. Can reuse `AUTH_GOOGLE_ID`. See [Google Drive](config-feature-google-drive.md) |
| `NEXT_PUBLIC_PLANTUML_SERVER_URL` | The URL of the PlantUML server, used for rendering UML diagrams. Allows using custom local servers. |
+| `NEXT_PUBLIC_POSTHOG_KEY` | (optional) Key for PostHog analytics. (see [deploy-analytics](deploy-analytics.md)) |
> Important: these variables must be set at build time, which is required by Next.js to pass them to the frontend.
> This is in contrast to the backend variables, which can be set when starting the local server/container.
diff --git a/package-lock.json b/package-lock.json
index 9cc0b5695..598fe22ac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.14.1",
+ "@googleworkspace/drive-picker-react": "^0.2.0",
"@mui/icons-material": "^5.18.0",
"@mui/joy": "^5.0.0-beta.52",
"@next/bundle-analyzer": "~15.1.11",
@@ -691,6 +692,25 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
+ "node_modules/@googleworkspace/drive-picker-element": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@googleworkspace/drive-picker-element/-/drive-picker-element-0.7.3.tgz",
+ "integrity": "sha512-z1hZh1HsPAQ19lencw2x3FcUVoymWYexcWgq66iXum4mUfWWaQ37oGtQ6hGvM8dyrC81G79P26gq7HhRtbGb2Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@googleworkspace/drive-picker-react": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@googleworkspace/drive-picker-react/-/drive-picker-react-0.2.0.tgz",
+ "integrity": "sha512-3CIEZ7U+HDKd8UoXG3l/fPSZFhxajC3MYNIqAZQSba2totuYKVTQMOJsgonO7OnnJq88YD35n7whCF4i5QUyvA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@googleworkspace/drive-picker-element": "0.7.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
diff --git a/package.json b/package.json
index ec0f5639d..f4604fe32 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.14.1",
+ "@googleworkspace/drive-picker-react": "^0.2.0",
"@mui/icons-material": "^5.18.0",
"@mui/joy": "^5.0.0-beta.52",
"@next/bundle-analyzer": "~15.1.11",
diff --git a/src/apps/chat/components/composer/Composer.tsx b/src/apps/chat/components/composer/Composer.tsx
index ba7b59b14..b4687d258 100644
--- a/src/apps/chat/components/composer/Composer.tsx
+++ b/src/apps/chat/components/composer/Composer.tsx
@@ -63,8 +63,10 @@ import { chatExecuteModeCanAttach, useChatExecuteMode } from '../../execute-mode
import { ButtonAttachCameraMemo, useCameraCaptureModalDialog } from './buttons/ButtonAttachCamera';
import { ButtonAttachClipboardMemo } from './buttons/ButtonAttachClipboard';
+import { ButtonAttachGoogleDriveMemo } from './buttons/ButtonAttachGoogleDrive';
import { ButtonAttachScreenCaptureMemo } from './buttons/ButtonAttachScreenCapture';
import { ButtonAttachWebMemo } from './buttons/ButtonAttachWeb';
+import { hasGoogleDriveCapability, useGoogleDrivePicker } from '~/common/attachment-drafts/useGoogleDrivePicker';
import { ButtonBeamMemo } from './buttons/ButtonBeam';
import { ButtonCallMemo } from './buttons/ButtonCall';
import { ButtonGroupDrawRepeat } from './buttons/ButtonGroupDrawRepeat';
@@ -197,7 +199,7 @@ export function Composer(props: {
const showChatAttachments = chatExecuteModeCanAttach(chatExecuteMode, props.capabilityHasT2IEdit);
const {
/* items */ attachmentDrafts,
- /* append */ attachAppendClipboardItems, attachAppendDataTransfer, attachAppendEgoFragments, attachAppendFile, attachAppendUrl,
+ /* append */ attachAppendClipboardItems, attachAppendCloudFile, attachAppendDataTransfer, attachAppendEgoFragments, attachAppendFile, attachAppendUrl,
/* take */ attachmentsRemoveAll, attachmentsTakeAllFragments, attachmentsTakeFragmentsByType,
} = useAttachmentDrafts(conversationOverlayStore, enableLoadURLsInComposer, chatLLMSupportsImages, handleFilterAGIFile, showChatAttachments === 'only-images');
@@ -623,6 +625,8 @@ export function Composer(props: {
const { openWebInputDialog, webInputDialogComponent } = useWebInputModal(handleAttachWebLinks, composeText);
+ const { openGoogleDrivePicker, googleDrivePickerComponent } = useGoogleDrivePicker(attachAppendCloudFile, isMobile);
+
// Attachments Down
@@ -802,6 +806,11 @@ export function Composer(props: {
+ {/* Responsive Google Drive button */}
+ {hasGoogleDriveCapability && }
+
{/* Responsive Paste button */}
{supportsClipboardRead() &&