1import type {
2 EditorId,
3 EnvironmentId,
4 ProjectScript,
5 ResolvedKeybindingsConfig,
6 ThreadId,
7} from "@t3tools/contracts";
8
9import type { DraftId } from "../../composerDraftStore";
10import { useT3ProjectFileScripts } from "../../hooks/useT3ProjectFileScripts";
11import { type EnvMode, type EnvironmentOption } from "../BranchToolbar.logic";
12import { BranchToolbar } from "../BranchToolbar";
13import BaseGitActionsControl from "../GitActionsControl";
14import ProjectScriptsControl, {
15 type NewProjectScriptInput,
16 type ProjectScriptActionResult,
17} from "../ProjectScriptsControl";
18import { memo, type ComponentProps } from "react";
19import { ThreadDetailsCard } from "./ThreadDetailsCard";
20import { OpenInPicker } from "./OpenInPicker";
21import { ThreadDetailsSection } from "./ThreadDetailsSection";
22import { ThreadAutomationsPanel } from "./ThreadAutomationsPanel";
23import { ThreadRelationshipsPanel } from "./ThreadRelationshipsControl";
24
25const GitActionsControl = memo(BaseGitActionsControl);
26
27export interface ThreadDetailsPanelProps extends Pick<
28 ComponentProps<typeof ThreadDetailsCard>,
29 "anchor" | "handle" | "onPresentationChange"
30> {
31 forceNewWorktree?: boolean;
32 environmentId: EnvironmentId;
33 threadId: ThreadId;
34 draftId?: DraftId;
35 activeProjectName: string | undefined;
36 activeProjectScripts: ReadonlyArray<ProjectScript> | undefined;
37 preferredScriptId: string | null;
38 keybindings: ResolvedKeybindingsConfig;
39 availableEditors: ReadonlyArray<EditorId>;
40 showOpenInPicker: boolean;
41 gitCwd: string | null;
42 isGitRepo: boolean;
43 envLocked: boolean;
44 availableEnvironments: readonly EnvironmentOption[];
45 autoEnvironmentLabel?: string | undefined;
46 onAutoEnvironment?: (() => void) | undefined;
47 onEnvironmentChange: (environmentId: EnvironmentId) => void;
48 onEnvModeChange: (mode: EnvMode) => void;
49 /** The thread's env mode as ChatView resolves it. */
50 envMode: EnvMode;
51 activeThreadBranchOverride?: string | null;
52 onActiveThreadBranchOverrideChange?: (branch: string | null) => void;
53 startFromOrigin: boolean;
54 onStartFromOriginChange: (startFromOrigin: boolean) => void;
55 onCheckoutPullRequestRequest?: (reference: string) => void;
56 onComposerFocusRequest: () => void;
57 onOpenChanges?: () => void;
58 onRunProjectScript: (script: ProjectScript) => void;
59 onAddProjectScript: (input: NewProjectScriptInput) => Promise<ProjectScriptActionResult>;
60 onUpdateProjectScript: (
61 scriptId: string,
62 input: NewProjectScriptInput,
63 ) => Promise<ProjectScriptActionResult>;
64 onDeleteProjectScript: (scriptId: string) => Promise<ProjectScriptActionResult>;
65}
66
67export function ThreadDetailsPanel(props: ThreadDetailsPanelProps) {
68 const fileScripts = useT3ProjectFileScripts(
69 props.environmentId,
70 props.activeProjectScripts ? props.gitCwd : null,
71 );
72 const activeThreadRef = { environmentId: props.environmentId, threadId: props.threadId };
73 const branchToolbarProps = {
74 showGitControls: props.isGitRepo,
75 environmentId: props.environmentId,
76 threadId: props.threadId,
77 ...(props.draftId ? { draftId: props.draftId } : {}),
78 onEnvModeChange: props.onEnvModeChange,
79 startFromOrigin: props.startFromOrigin,
80 onStartFromOriginChange: props.onStartFromOriginChange,
81 envMode: props.envMode,
82 ...(props.activeThreadBranchOverride !== undefined
83 ? { activeThreadBranchOverride: props.activeThreadBranchOverride }
84 : {}),
85 ...(props.onActiveThreadBranchOverrideChange
86 ? { onActiveThreadBranchOverrideChange: props.onActiveThreadBranchOverrideChange }
87 : {}),
88 envLocked: props.envLocked,
89 forceNewWorktree: props.forceNewWorktree ?? false,
90 onComposerFocusRequest: props.onComposerFocusRequest,
91 ...(props.onCheckoutPullRequestRequest
92 ? { onCheckoutPullRequestRequest: props.onCheckoutPullRequestRequest }
93 : {}),
94 };
95
96 return (
97 <ThreadDetailsCard
98 threadRef={{ environmentId: props.environmentId, threadId: props.threadId }}
99 anchor={props.anchor}
100 handle={props.handle}
101 onPresentationChange={props.onPresentationChange}
102 >
103 {(density) => (
104 <>
105 <ThreadDetailsSection
106 headingId="thread-details-workspace-heading"
107 title="Workspace"
108 separated={false}
109 showHeading={false}
110 >
111 <div className="flex flex-col">
112 {density === "full" ? (
113 <BranchToolbar
114 layout="panel"
115 panelSection="workspace"
116 availableEnvironments={props.availableEnvironments}
117 onEnvironmentChange={props.onEnvironmentChange}
118 autoEnvironmentLabel={props.autoEnvironmentLabel}
119 onAutoEnvironment={props.onAutoEnvironment}
120 {...branchToolbarProps}
121 />
122 ) : null}
123
124 {density !== "essential" && props.showOpenInPicker ? (
125 <OpenInPicker
126 keybindings={props.keybindings}
127 environmentId={props.environmentId}
128 availableEditors={props.availableEditors}
129 openInCwd={props.gitCwd}
130 displayMode="panel"
131 />
132 ) : null}
133
134 {props.activeProjectScripts ? (
135 <ProjectScriptsControl
136 environmentId={props.environmentId}
137 displayMode="panel"
138 scripts={props.activeProjectScripts}
139 fileScripts={fileScripts}
140 preferredScriptId={props.preferredScriptId}
141 onRunScript={props.onRunProjectScript}
142 onAddScript={props.onAddProjectScript}
143 onUpdateScript={props.onUpdateProjectScript}
144 onDeleteScript={props.onDeleteProjectScript}
145 />
146 ) : null}
147 </div>
148 </ThreadDetailsSection>
149
150 {props.gitCwd ? (
151 <ThreadDetailsSection
152 headingId="thread-details-version-control-heading"
153 title="Version Control"
154 showHeading={false}
155 separated={density === "full"}
156 >
157 <div className="flex flex-col">
158 {props.isGitRepo ? (
159 <BranchToolbar layout="panel" panelSection="branch" {...branchToolbarProps} />
160 ) : null}
161 {props.activeProjectName ? (
162 <GitActionsControl
163 displayMode="panel"
164 compact={density !== "full"}
165 gitCwd={props.gitCwd}
166 activeThreadRef={activeThreadRef}
167 {...(props.draftId ? { draftId: props.draftId } : {})}
168 {...(props.onOpenChanges ? { onOpenChanges: props.onOpenChanges } : {})}
169 />
170 ) : null}
171 </div>
172 </ThreadDetailsSection>
173 ) : null}
174
175 {density === "full" && !props.draftId ? (
176 <ThreadAutomationsPanel environmentId={props.environmentId} threadId={props.threadId} />
177 ) : null}
178
179 {density === "full" && !props.draftId ? (
180 <ThreadRelationshipsPanel
181 environmentId={props.environmentId}
182 threadId={props.threadId}
183 />
184 ) : null}
185 </>
186 )}
187 </ThreadDetailsCard>
188 );
189}