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
21 changes: 18 additions & 3 deletions apps/web/app/sessions/[sessionId]/chats/[chatId]/chat-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { useIsMobile } from "@/hooks/use-mobile";
import { useGitPanel } from "./git-panel-context";

type ChatTabsProps = {
Expand All @@ -51,6 +52,8 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
setFileTabDismissed,
} = useGitPanel();

const isMobile = useIsMobile();

const [renamingChatId, setRenamingChatId] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState("");
const [deletingChatId, setDeletingChatId] = useState<string | null>(null);
Expand Down Expand Up @@ -226,7 +229,10 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
<button
type="button"
onClick={handleCloseChanges}
className="mr-1 rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100"
className={cn(
"mr-1 rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent hover:text-foreground",
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
)}
>
<X className="h-3 w-3" />
</button>
Expand Down Expand Up @@ -254,7 +260,10 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
<button
type="button"
onClick={handleCloseFile}
className="mr-1 rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100"
className={cn(
"mr-1 rounded p-0.5 text-muted-foreground transition-opacity hover:bg-accent hover:text-foreground",
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
)}
>
<X className="h-3 w-3" />
</button>
Expand Down Expand Up @@ -326,7 +335,12 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
)}

{!isRenaming && (
<div className="flex items-center gap-0.5 pr-1 opacity-0 transition-opacity group-hover:opacity-100">
<div
className={cn(
"flex items-center gap-0.5 pr-1 transition-opacity",
isMobile ? "opacity-100" : "opacity-0 group-hover:opacity-100",
)}
>
<button
type="button"
onClick={(e) => {
Expand Down Expand Up @@ -378,6 +392,7 @@ export function ChatTabs({ activeChatId }: ChatTabsProps) {
handleFinishRename,
handleStartRename,
insertAt,
isMobile,
prefetchChat,
renameValue,
renamingChatId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type ReactNode,
type RefObject,
} from "react";
import { useIsMobile } from "@/hooks/use-mobile";

export type GitPanelTab = "diff" | "pr" | "files";
export type ActiveView = "chat" | "diff" | "file";
Expand Down Expand Up @@ -83,6 +84,7 @@ const GitPanelContext = createContext<GitPanelContextValue | undefined>(
);

export function GitPanelProvider({ children }: { children: ReactNode }) {
const isMobile = useIsMobile();
const [gitPanelOpen, setGitPanelOpen] = useState(false);
const [gitPanelTab, setGitPanelTab] = useState<GitPanelTab>("files");
const [activeView, setActiveView] = useState<ActiveView>("chat");
Expand All @@ -99,18 +101,26 @@ export function GitPanelProvider({ children }: { children: ReactNode }) {
const panelPortalRef = useRef<HTMLDivElement | null>(null);
const headerActionsRef = useRef<HTMLDivElement | null>(null);

const openDiffToFile = useCallback((filePath: string) => {
setFocusedDiffFile(filePath);
setFocusedDiffRequestId((prev) => prev + 1);
setActiveView("diff");
setChangesTabDismissed(false);
}, []);

const openFileTab = useCallback((filePath: string) => {
setFocusedFilePath(filePath);
setActiveView("file");
setFileTabDismissed(false);
}, []);
const openDiffToFile = useCallback(
(filePath: string) => {
setFocusedDiffFile(filePath);
setFocusedDiffRequestId((prev) => prev + 1);
setActiveView("diff");
setChangesTabDismissed(false);
if (isMobile) setGitPanelOpen(false);
},
[isMobile],
);

const openFileTab = useCallback(
(filePath: string) => {
setFocusedFilePath(filePath);
setActiveView("file");
setFileTabDismissed(false);
if (isMobile) setGitPanelOpen(false);
},
[isMobile],
);

const value = useMemo(
() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import { FileSuggestionsDropdown } from "@/components/file-suggestions-dropdown"
import { ImageAttachmentsPreview } from "@/components/image-attachments-preview";
import { TextAttachmentsPreview } from "@/components/text-attachments-preview";
import { ModelSelectorCompact } from "@/components/model-selector-compact";
import { QuestionPanel } from "@/components/question-panel";
import { useInlineQuestion } from "@/components/inline-question-input";
import { SlashCommandDropdown } from "@/components/slash-command-dropdown";
import { SnippetChip } from "@/components/snippet-chip";
import { AssistantMessageGroups } from "@/components/assistant-message-groups";
Expand Down Expand Up @@ -2638,6 +2638,26 @@ export function SessionChatContent({
}
}, [questionToolCallId, addToolOutput]);

// Stable empty array so the hook doesn't reset on every render when there's no question
const emptyQuestions = useMemo(
() => [] as AskUserQuestionInput["questions"],
[],
);

const inlineQuestion = useInlineQuestion({
questions:
hasPendingQuestion && pendingQuestionPart
? pendingQuestionPart.input.questions
: emptyQuestions,
onSubmit: handleQuestionSubmit,
onCancel: handleQuestionCancel,
textareaValue: input,
onTextareaChange: setInput,
});

// Inline question UI is integrated into the prompt box on all viewports
const showInlineQuestion = inlineQuestion.isActive;

const isReconnectingSandbox =
reconnectionStatus === "checking" &&
!sandboxInfo &&
Expand Down Expand Up @@ -3656,15 +3676,6 @@ export function SessionChatContent({
)}
</div>

{/* Question Panel */}
{hasPendingQuestion && pendingQuestionPart && (
<QuestionPanel
questions={pendingQuestionPart.input.questions}
onSubmit={handleQuestionSubmit}
onCancel={handleQuestionCancel}
/>
)}

{/* Input */}
<div className="p-4 pb-2 sm:pb-8">
<div className="mx-auto max-w-4xl space-y-2">
Expand Down Expand Up @@ -3755,6 +3766,8 @@ export function SessionChatContent({
<form
onSubmit={async (e) => {
e.preventDefault();
// When inline question is active, don't send a chat message
if (showInlineQuestion) return;
if (
isArchived ||
!isSandboxActive ||
Expand Down Expand Up @@ -3960,12 +3973,19 @@ export function SessionChatContent({
</div>
)}

{/* Inline question UI for mobile — rendered inside prompt box */}
{showInlineQuestion && inlineQuestion.questionHeaderUI}

{/* Textarea area */}
<div className="px-4 pb-2 pt-3">
<textarea
ref={inputRef}
value={input}
placeholder="Request changes or ask a question..."
placeholder={
showInlineQuestion
? inlineQuestion.placeholder
: "Request changes or ask a question..."
}
rows={1}
onFocus={handleTextareaFocus}
onChange={(e) => {
Expand All @@ -3975,6 +3995,16 @@ export function SessionChatContent({
);
}}
onKeyDown={(e) => {
// When inline question is active, Enter advances the question
if (
showInlineQuestion &&
e.key === "Enter" &&
!e.shiftKey
) {
e.preventDefault();
inlineQuestion.handleNext();
return;
}
// Let suggestions handle keyboard events first
if (handleSuggestionsKeyDown(e)) {
return;
Expand Down Expand Up @@ -4144,7 +4174,21 @@ export function SessionChatContent({
)}
</Button>

{isChatInFlight || hasPendingResponse ? (
{showInlineQuestion ? (
<Button
type="button"
size="sm"
onClick={(e) => {
e.preventDefault();
inlineQuestion.handleNext();
}}
disabled={!inlineQuestion.hasCurrentAnswer}
className="h-8 rounded-full bg-primary px-3 text-xs text-primary-foreground hover:bg-primary/90 disabled:opacity-30"
>
<Check className="mr-1 h-3 w-3" />
{inlineQuestion.buttonLabel}
</Button>
) : isChatInFlight || hasPendingResponse ? (
<Button
type="button"
size="icon"
Expand Down
10 changes: 7 additions & 3 deletions apps/web/components/inbox-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -863,9 +863,10 @@ export function InboxSidebar({

const handleCreateForRepo = useCallback(
(owner: string, repo: string) => {
if (isMobile) setOpenMobile(false);
onCreateSessionForRepo(owner, repo);
},
[onCreateSessionForRepo],
[isMobile, setOpenMobile, onCreateSessionForRepo],
);

const handleOpenBranchPicker = useCallback((owner: string, repo: string) => {
Expand All @@ -883,13 +884,14 @@ export function InboxSidebar({
branch,
);
setBranchPickerRepo(null);
if (isMobile) setOpenMobile(false);
} catch (error) {
console.error("Failed to create session from branch:", error);
} finally {
setIsCreatingFromBranch(false);
}
},
[branchPickerRepo, onCreateSessionFromBranch],
[branchPickerRepo, onCreateSessionFromBranch, isMobile, setOpenMobile],
);

return (
Expand Down Expand Up @@ -1022,7 +1024,9 @@ export function InboxSidebar({
</span>
</button>
{hasRepo ? (
<span className="hidden shrink-0 items-center gap-0.5 group-hover/repo:flex">
<span
className={`shrink-0 items-center gap-0.5 ${isMobile ? "flex" : "hidden group-hover/repo:flex"}`}
>
<Tooltip>
<TooltipTrigger asChild>
<button
Expand Down
Loading
Loading