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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This is a fork of T3 Code that integrates a few additional features, including:
- "Chat + Replies" keeps the main list short: it shows only first messages, and the replies to a message show in a Replies tab in the right panel (+ → Replies, or the reply line under a message). Point at a message and choose Reply to answer under it; the tab has its own reply box. Under each first message you see how many replies it has, how many are new, and a few generated words on what they are about. The agent answers a message with `send_to_user` and that message's id, so each answer lands under the message it answers. A Cite in the main list opens that message's replies and puts the quote in their reply box, unless the main box or the open reply box already has text; then the quote goes there, the main box first.
- In all three modes the agent sends one reply per message you type, and before a long task it may first send a short "on it" note; a thin line parts two agent messages in a row. To say more, it rewrites its reply in place with its `replace_send_to_user` tool. A rewritten reply shows a small "Edited" mark, and in Chat + Replies it counts as new.
- Settings → Threads → New threads → Display mode picks the mode for threads you start from the message box in the web or desktop app. Threads that agents, scheduled tasks, side chats or the mobile app start open in Normal, and existing threads keep their mode.
- **Split the right panel in two:** drag a right-panel tab onto the left or right part of the panel's content and drop it to get two panes side by side, each with its own tabs, as with Windows Snap. Drag tabs between the panes, drag the line between them to resize (double-click resets it), and close or move away a pane's last tab to go back to one pane. The tab menu has Split right, Split left, Move to the other pane and Unsplit. New tabs open in the pane you last used. A narrow window shows all tabs in one strip and keeps the split for when there is room. A press anywhere on a tab now switches to it at once.
- Copy buttons on pages in the preview browser now put the copied text into your own clipboard.
- **Two preview browser modes:** Settings → Browser → Preview browser picks the browser the server runs for preview tabs. **T3 native** (fast) is the default. It runs chrome-headless-shell, Chrome's small build that runs without a window. **Chrome** (slow) runs full Chrome, for pages that don't work in T3 native. On platforms where T3 has no Chrome for Testing build (Google's Chrome build for test automation), both choices run T3 native. Switching closes the open preview tabs.
- **Compact when idle for Claude threads:** a choice in Settings → Providers → Advanced → Compact idle threads. Besides compacting before the prompt cache expires, it compacts a large idle Claude thread (default 400K tokens or more) a set time (default 15 minutes) after the last message or answer from a user or a bot, so the compaction runs while no one waits for a reply. The token counts and the wait are set right under the choice.
Expand Down
132 changes: 72 additions & 60 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ import {
selectActiveRightPanelSurface,
selectThreadPanelOpen,
selectThreadRightPanelState,
type RightPanelSurface,
useRightPanelStore,
} from "../rightPanelStore";
import { isPreviewSupportedInRuntime, useThreadPreviewState } from "../previewStateStore";
Expand Down Expand Up @@ -364,6 +365,8 @@ import { observeResize } from "~/lib/observeResize";
import { ThreadVisitWatermark } from "./chat/ThreadVisitWatermark";
import { ChatTerminalDrawers } from "./chat/ChatTerminalDrawers";
import { ChatRightPanel } from "./chat/ChatRightPanel";
import { useSplitRendered } from "../fork/rightPanelSplit/renderedSplit";
import { unfocusedPaneSurface } from "../fork/rightPanelSplit/splitLayout.logic";
import { useDashboardSurfaceAdder } from "../fork/dashboard/dashboardSurface";
import { REPLY_BOX_QUEUE_EDIT_BLOCKED, subThreadDraftId } from "../fork/subThreads/subThreadReply";
import { useSubThreadCite } from "../fork/subThreads/subThreadCite";
Expand Down Expand Up @@ -1083,6 +1086,7 @@ export default function ChatView(props: ChatViewProps) {
const rightPanelPresenceValue = {
activeSurface: activeRightPanelSurface,
surfaces: rightPanelState.surfaces,
split: rightPanelState.split,
};
const rightPanelPresence = usePanelPresence(
rightPanelOpen && activeThreadRef !== null,
Expand All @@ -1097,10 +1101,16 @@ export default function ChatView(props: ChatViewProps) {
const rightPanelControlsAtRoot = rightPanelPresent && !shouldUsePlanSidebarSheet;
const renderedRightPanelSurface = rightPanelPresence.value?.activeSurface ?? null;
const renderedRightPanelSurfaces = rightPanelPresence.value?.surfaces ?? [];
const previewMiniPlayerVisible = shouldRenderPreviewMiniPlayer(
activePreviewMiniPlayer?.source ?? null,
renderedRightPanelSurface,
);
const rightPanelSplitRendered = useSplitRendered(activeThreadKey);
const previewMiniPlayerVisible =
shouldRenderPreviewMiniPlayer(
activePreviewMiniPlayer?.source ?? null,
renderedRightPanelSurface,
) &&
shouldRenderPreviewMiniPlayer(
activePreviewMiniPlayer?.source ?? null,
rightPanelSplitRendered ? unfocusedPaneSurface(rightPanelState) : null,
);
const canMaximizeRightPanel = rightPanelOpen;
// An embedded side chat has no panel; a stale maximized flag would hide its own column.
const rightPanelMaximized =
Expand Down Expand Up @@ -3210,59 +3220,60 @@ export default function ChatView(props: ChatViewProps) {
return embedded ? null : <NoActiveThreadState />;
}

const rightPanelContent = activeThreadRef ? (
<RightPanelSurfaceContent
surface={renderedRightPanelSurface}
activeSurfaceId={activeRightPanelSurface?.id ?? null}
threadRef={activeThreadRef}
environmentId={activeThread.environmentId}
visible={rightPanelOpen}
composerDraftTarget={composerDraftTarget}
keybindings={keybindings}
configuredPreviewUrls={configuredPreviewUrls}
workspaceMutationId={workspaceMutationId}
getShortcutContext={getShortcutContextStable}
onSendAnnotation={sendPreviewAnnotationStable}
onCloseSurface={closeRightPanelSurface}
sideChatParentPanel={sideChatParentPanel}
onCiteAssistantText={citeAssistantTextStable}
terminal={{
launchContext: activeTerminalLaunchContext ?? null,
keybindings,
onAddTerminalContext: addTerminalContextToDraftStable,
onSplitTerminal: splitPanelTerminalStable,
onSplitTerminalVertical: splitPanelTerminalVerticalStable,
onNewTerminal: addTerminalSurfaceStable,
onActiveTerminalChange: activatePanelTerminalStable,
onCloseTerminal: closePanelTerminalStable,
splitShortcutLabel: splitTerminalShortcutLabel ?? undefined,
splitVerticalShortcutLabel: splitTerminalVerticalShortcutLabel ?? undefined,
newShortcutLabel: newTerminalShortcutLabel ?? undefined,
closeShortcutLabel: closeTerminalShortcutLabel ?? undefined,
}}
pullRequest={{
capabilityKnown: pullRequestsCapabilityKnown,
supported: supportsPullRequests,
projectId: activeThread.projectId,
pullRequests: visiblePullRequests,
linkedPullRequest: linkedThreadPullRequest,
branchPullRequest: activeThreadShell?.branchPullRequest ?? activeThread.branchPullRequest,
onBack:
pullRequestsSurfaceAvailable && visiblePullRequestCount > 1
? addPullRequestsSurfaceStable
: undefined,
}}
files={{
projectId: activeProject?.id,
projectName: activeProject?.title ?? null,
workspaceRoot: activeWorkspaceRoot,
availableEditors,
pendingSurfaceIds: pendingFileSurfaceIds,
onOpenFile: openFileSurfaceStable,
onPendingChange: handleFilePendingChangeStable,
}}
/>
) : null;
const renderRightPanelSurface = (surface: RightPanelSurface, visible: boolean) =>
activeThreadRef ? (
<RightPanelSurfaceContent
surface={surface}
activeSurfaceId={activeRightPanelSurface?.id ?? null}
threadRef={activeThreadRef}
environmentId={activeThread.environmentId}
visible={visible}
composerDraftTarget={composerDraftTarget}
keybindings={keybindings}
configuredPreviewUrls={configuredPreviewUrls}
workspaceMutationId={workspaceMutationId}
getShortcutContext={getShortcutContextStable}
onSendAnnotation={sendPreviewAnnotationStable}
onCloseSurface={closeRightPanelSurface}
sideChatParentPanel={sideChatParentPanel}
onCiteAssistantText={citeAssistantTextStable}
terminal={{
launchContext: activeTerminalLaunchContext ?? null,
keybindings,
onAddTerminalContext: addTerminalContextToDraftStable,
onSplitTerminal: splitPanelTerminalStable,
onSplitTerminalVertical: splitPanelTerminalVerticalStable,
onNewTerminal: addTerminalSurfaceStable,
onActiveTerminalChange: activatePanelTerminalStable,
onCloseTerminal: closePanelTerminalStable,
splitShortcutLabel: splitTerminalShortcutLabel ?? undefined,
splitVerticalShortcutLabel: splitTerminalVerticalShortcutLabel ?? undefined,
newShortcutLabel: newTerminalShortcutLabel ?? undefined,
closeShortcutLabel: closeTerminalShortcutLabel ?? undefined,
}}
pullRequest={{
capabilityKnown: pullRequestsCapabilityKnown,
supported: supportsPullRequests,
projectId: activeThread.projectId,
pullRequests: visiblePullRequests,
linkedPullRequest: linkedThreadPullRequest,
branchPullRequest: activeThreadShell?.branchPullRequest ?? activeThread.branchPullRequest,
onBack:
pullRequestsSurfaceAvailable && visiblePullRequestCount > 1
? addPullRequestsSurfaceStable
: undefined,
}}
files={{
projectId: activeProject?.id,
projectName: activeProject?.title ?? null,
workspaceRoot: activeWorkspaceRoot,
availableEditors,
pendingSurfaceIds: pendingFileSurfaceIds,
onOpenFile: openFileSurfaceStable,
onPendingChange: handleFilePendingChangeStable,
}}
/>
) : null;
const panelToggleControlProps = {
terminalAvailable: activeProject !== null,
terminalOpen: terminalUiState.terminalOpen,
Expand Down Expand Up @@ -4027,9 +4038,10 @@ export default function ChatView(props: ChatViewProps) {
deviceAvailable={activeThreadRef !== null}
sideChatAvailable={sideChat.available}
sideChatDisabledReason={sideChatDisabledReason}
>
{rightPanelContent}
</ChatRightPanel>
threadRef={activeThreadRef}
split={rightPanelPresence.value?.split}
renderSurface={renderRightPanelSurface}
/>
) : null}

{subThreadDraft === null ? (
Expand Down
Loading
Loading