Skip to content
Merged
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
46 changes: 40 additions & 6 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import PlanSidebar from "./PlanSidebar";
import ThreadTerminalDrawer from "./ThreadTerminalDrawer";
import { SpotifyPlayerDrawer } from "./SpotifyPlayer";
import {
AtSignIcon,
BotIcon,
ChevronDownIcon,
ChevronLeftIcon,
Expand Down Expand Up @@ -345,6 +346,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
const promptRef = useRef(prompt);
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const [isDragOverComposer, setIsDragOverComposer] = useState(false);
const [dragOverType, setDragOverType] = useState<"files" | "tree-path">("files");
const [expandedImage, setExpandedImage] = useState<ExpandedImagePreview | null>(null);
const [optimisticUserMessages, setOptimisticUserMessages] = useState<ChatMessage[]>([]);
const optimisticUserMessagesRef = useRef(optimisticUserMessages);
Expand Down Expand Up @@ -2444,17 +2446,25 @@ export default function ChatView({ threadId }: ChatViewProps) {
addComposerImages(imageFiles);
};

const isAcceptedDragType = (dataTransfer: DataTransfer) =>
dataTransfer.types.includes("Files") ||
dataTransfer.types.includes("application/x-okcode-tree-path");

const isDragTreePath = (dataTransfer: DataTransfer) =>
dataTransfer.types.includes("application/x-okcode-tree-path");

const onComposerDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
if (!event.dataTransfer.types.includes("Files")) {
if (!isAcceptedDragType(event.dataTransfer)) {
return;
}
event.preventDefault();
dragDepthRef.current += 1;
setDragOverType(isDragTreePath(event.dataTransfer) ? "tree-path" : "files");
setIsDragOverComposer(true);
};

const onComposerDragOver = (event: React.DragEvent<HTMLDivElement>) => {
if (!event.dataTransfer.types.includes("Files")) {
if (!isAcceptedDragType(event.dataTransfer)) {
return;
}
event.preventDefault();
Expand All @@ -2463,7 +2473,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
};

const onComposerDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
if (!event.dataTransfer.types.includes("Files")) {
if (!isAcceptedDragType(event.dataTransfer)) {
return;
}
event.preventDefault();
Expand All @@ -2478,12 +2488,27 @@ export default function ChatView({ threadId }: ChatViewProps) {
};

const onComposerDrop = (event: React.DragEvent<HTMLDivElement>) => {
if (!event.dataTransfer.types.includes("Files")) {
if (!isAcceptedDragType(event.dataTransfer)) {
return;
}
event.preventDefault();
dragDepthRef.current = 0;
setIsDragOverComposer(false);

// Handle file tree path drops — insert as @mention context
if (isDragTreePath(event.dataTransfer)) {
const treePath = event.dataTransfer.getData("application/x-okcode-tree-path");
if (treePath) {
const snapshot = readComposerSnapshot();
const mention = `@${treePath} `;
// Insert at the current cursor position
applyPromptReplacement(snapshot.cursor, snapshot.cursor, mention);
}
focusComposer();
return;
}

// Handle image file drops
const files = Array.from(event.dataTransfer.files);
addComposerImages(files);
focusComposer();
Expand Down Expand Up @@ -4007,8 +4032,17 @@ export default function ChatView({ threadId }: ChatViewProps) {
{isDragOverComposer && (
<div className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-[22px] border-2 border-dashed border-primary/60 bg-primary/5">
<div className="flex items-center gap-2 rounded-lg bg-background/90 px-4 py-2 text-sm font-medium text-primary shadow-sm">
<ImagePlusIcon className="size-4" />
Drop images to attach
{dragOverType === "tree-path" ? (
<>
<AtSignIcon className="size-4" />
Drop to add as context
</>
) : (
<>
<ImagePlusIcon className="size-4" />
Drop images to attach
</>
)}
</div>
</div>
)}
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/components/WorkspaceFileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ const WorkspaceDirectoryRow = memo(function WorkspaceDirectoryRow(props: {
return (
<button
type="button"
draggable
onDragStart={(event) => {
event.dataTransfer.setData("application/x-okcode-tree-path", props.entry.path);
event.dataTransfer.effectAllowed = "copy";
}}
className={cn(
"group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left hover:bg-accent/60",
!props.entry.hasChildren && "cursor-default",
Expand Down Expand Up @@ -213,6 +218,11 @@ const WorkspaceFileRow = memo(function WorkspaceFileRow(props: {
return (
<button
type="button"
draggable
onDragStart={(event) => {
event.dataTransfer.setData("application/x-okcode-tree-path", props.entry.path);
event.dataTransfer.effectAllowed = "copy";
}}
className="group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left hover:bg-accent/60"
style={{ paddingLeft: `${leftPadding}px` }}
onClick={(event) =>
Expand Down
Loading