diff --git a/apps/web/src/components/app/context-picker.tsx b/apps/web/src/components/app/context-picker.tsx
new file mode 100644
index 00000000..a8d35748
--- /dev/null
+++ b/apps/web/src/components/app/context-picker.tsx
@@ -0,0 +1,609 @@
+import {
+ type ChangeEvent,
+ type ClipboardEvent,
+ useCallback,
+ useEffect,
+ useId,
+ useRef,
+ useState,
+} from "react";
+import {
+ ChevronLeft,
+ Clipboard,
+ FileText,
+ Link2,
+ Paperclip,
+ Plus,
+ Upload,
+ X,
+} from "lucide-react";
+
+import {
+ type ClipboardSuggestion,
+ STARTUP_FILE_ACCEPT,
+ createClipboardSuggestionFromText,
+ getClipboardFilesFromEvent,
+ getClipboardSuggestion,
+ normalizeUrl,
+ startupFileExt,
+ startupFileKey,
+ startupLinkLabel,
+} from "@/components/app/create-agent-dialog-clipboard";
+import { ActivityBars } from "@/components/ui/activity-bars";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { cn } from "@/lib/utils";
+
+function AddContextMenu({
+ onAddFile,
+ onAddLink,
+}: {
+ onAddFile: () => void;
+ onAddLink: () => void;
+}) {
+ return (
+
+
+
+
+ );
+}
+
+function AddContextLinkForm({
+ value,
+ onChange,
+ onSubmit,
+ onBack,
+ isValid,
+ inputId,
+ errorId,
+ testIdPrefix,
+}: {
+ value: string;
+ onChange: (next: string) => void;
+ onSubmit: () => void;
+ onBack: () => void;
+ isValid: boolean;
+ inputId: string;
+ errorId: string;
+ testIdPrefix?: string;
+}) {
+ return (
+
+
+
onChange(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ if (value.trim().length > 0 && isValid) {
+ onSubmit();
+ }
+ }
+ }}
+ placeholder="https://..."
+ aria-invalid={!isValid}
+ aria-describedby={!isValid ? errorId : undefined}
+ {...(testIdPrefix
+ ? { "data-testid": `${testIdPrefix}-link-input` }
+ : {})}
+ />
+ {!isValid ? (
+
+ Enter a valid `http:` or `https:` URL.
+
+ ) : null}
+
+
+
+
+
+ );
+}
+
+export type ContextPickerProps = {
+ files: File[];
+ links: string[];
+ draggingFiles: boolean;
+ filePreviewsRef: React.MutableRefObject
@@ -1428,7 +972,7 @@ function CreateAgentDialogContent({
variant="primary"
tabIndex={0}
className="min-h-11 px-3"
- disabled={creating || !linkDraftIsValid}
+ disabled={creating || contextDraftInvalid}
data-testid="create-agent-context-submit"
>
{creating ? (
diff --git a/e2e/terminal-agent-type.spec.ts b/e2e/terminal-agent-type.spec.ts
index be476201..0d1a0420 100644
--- a/e2e/terminal-agent-type.spec.ts
+++ b/e2e/terminal-agent-type.spec.ts
@@ -264,9 +264,7 @@ test.describe("Terminal agent type", () => {
await expect(
page.locator('[title="https://example.com/docs/launch-context"]')
).not.toBeVisible();
- await page
- .getByTestId("create-agent-context-clipboard-check-action")
- .click();
+ await page.getByTestId("create-agent-context-clipboard-action").click();
await expect(
page.locator('[title="https://example.com/docs/launch-context"]')
@@ -285,9 +283,7 @@ test.describe("Terminal agent type", () => {
await page.getByTestId("create-agent-button").click();
await page.getByTestId("create-agent-with-context").click();
- await page
- .getByTestId("create-agent-context-clipboard-check-action")
- .click();
+ await page.getByTestId("create-agent-context-clipboard-action").click();
await expect(
page.locator('[title="https://example.com/rich-link"]')
@@ -330,16 +326,14 @@ test.describe("Terminal agent type", () => {
await page.getByTestId("create-agent-button").click();
await page.getByTestId("create-agent-with-context").click();
- await expect(
- page.getByTestId("create-agent-context-clipboard-check")
- ).toBeVisible();
+ await expect(page.getByTestId("create-agent-context")).toBeVisible();
const readClipboard = page.getByTestId(
- "create-agent-context-clipboard-check-action"
+ "create-agent-context-clipboard-action"
);
await readClipboard.click();
await expect(
page.getByTestId("create-agent-context-clipboard-feedback")
- ).toContainText("Nothing readable found.");
+ ).toContainText("Nothing readable found on the clipboard.");
await readClipboard.click();
await expect(
@@ -347,7 +341,7 @@ test.describe("Terminal agent type", () => {
).toBeVisible();
});
- test("create with context shows feedback when clipboard access is blocked", async ({
+ test("create with context enters paste mode when clipboard access is blocked", async ({
page,
}) => {
await stubClipboard(page, { kind: "blocked" });
@@ -357,18 +351,15 @@ test.describe("Terminal agent type", () => {
await page.getByTestId("create-agent-with-context").click();
const readButton = page.getByTestId(
- "create-agent-context-clipboard-check-action"
+ "create-agent-context-clipboard-action"
);
await expect(readButton).toBeVisible();
await readButton.click();
- await expect(readButton).toBeVisible();
- await expect(
- page.getByTestId("create-agent-context-clipboard-feedback")
- ).toContainText("Clipboard access was blocked.");
+ await expect(page.getByPlaceholder("Paste here")).toBeVisible();
});
- test("create with context shows the read button even when clipboard APIs are unavailable", async ({
+ test("create with context enters paste mode when clipboard APIs are unavailable", async ({
page,
}) => {
await stubClipboard(page, { kind: "unsupported" });
@@ -378,14 +369,12 @@ test.describe("Terminal agent type", () => {
await page.getByTestId("create-agent-with-context").click();
const readButton = page.getByTestId(
- "create-agent-context-clipboard-check-action"
+ "create-agent-context-clipboard-action"
);
await expect(readButton).toBeVisible();
await readButton.click();
- await expect(
- page.getByTestId("create-agent-context-clipboard-feedback")
- ).toContainText("Clipboard access isn't available here.");
+ await expect(page.getByPlaceholder("Paste here")).toBeVisible();
});
test("create with context auto-adds pasted URLs as link tiles in the prompt textarea", async ({
@@ -483,9 +472,7 @@ test.describe("Terminal agent type", () => {
const prompt = page.getByTestId("create-agent-initial-prompt");
await prompt.fill(" \n\n ");
- await page
- .getByTestId("create-agent-context-clipboard-check-action")
- .click();
+ await page.getByTestId("create-agent-context-clipboard-action").click();
await expect(prompt).toHaveValue("Real instructions from clipboard.");
});
@@ -502,9 +489,7 @@ test.describe("Terminal agent type", () => {
await page.getByTestId("create-agent-button").click();
await page.getByTestId("create-agent-with-context").click();
- await page
- .getByTestId("create-agent-context-clipboard-check-action")
- .click();
+ await page.getByTestId("create-agent-context-clipboard-action").click();
await expect(page.getByText("clipboard-image.png")).toBeVisible();
});