Skip to content

Commit 3c3b7eb

Browse files
committed
Start hooking up issue buttons
1 parent ae3b6da commit 3c3b7eb

File tree

5 files changed

+133
-44
lines changed

5 files changed

+133
-44
lines changed

src/github/dashboardWebviewProvider.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,6 @@ export class DashboardWebviewProvider extends WebviewBase {
470470
private getTargetRepositories(): string[] {
471471
// If explicit repos are configured, use those
472472
if (this._repos) {
473-
474473
return this._repos;
475474
}
476475

@@ -511,7 +510,6 @@ export class DashboardWebviewProvider extends WebviewBase {
511510

512511
return Promise.all(searchResult.items.map(issue => this.convertIssueToData(issue)));
513512
} catch (error) {
514-
515513
return [];
516514
}
517515
}
@@ -1140,7 +1138,7 @@ Keep your response focused and actionable - ask at most 3 essential questions if
11401138
* Determines if a query represents a coding task vs a general question using VS Code's Language Model API
11411139
*/
11421140
private async isCodingTask(query: string): Promise<boolean> {
1143-
return await this._taskManager.isCodingTask(query);
1141+
return this._taskManager.isCodingTask(query);
11441142
}
11451143

11461144
/**
@@ -1150,7 +1148,7 @@ Keep your response focused and actionable - ask at most 3 essential questions if
11501148
* Shows a quick pick to let user choose between local and remote work
11511149
*/
11521150
private async showWorkModeQuickPick(): Promise<'local' | 'remote' | undefined> {
1153-
return await this._taskManager.showWorkModeQuickPick();
1151+
return this._taskManager.showWorkModeQuickPick();
11541152
}
11551153

11561154
/**

webviews/dashboardView/app.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ function Dashboard() {
4040
const [issueSort, setIssueSort] = useState<'date-oldest' | 'date-newest'>('date-oldest');
4141
const [hoveredIssue, setHoveredIssue] = useState<IssueData | null>(null);
4242
const [globalFilter, setGlobalFilter] = useState<FilterState>({ showTasks: true, showProjects: true });
43+
const [chatInputValue, setChatInputValue] = useState('');
44+
const [focusTrigger, setFocusTrigger] = useState(0);
4345

4446
useEffect(() => {
4547
// Listen for messages from the extension
@@ -90,12 +92,18 @@ function Dashboard() {
9092
});
9193
}, []);
9294

93-
const handleStartRemoteAgent = useCallback((issue: any, event: React.MouseEvent) => {
95+
const handlePopulateLocalInput = useCallback((issue: any, event: React.MouseEvent) => {
9496
event.stopPropagation(); // Prevent triggering the issue click
95-
vscode.postMessage({
96-
command: 'start-remote-agent',
97-
args: { issue }
98-
});
97+
const command = `@local start work on #${issue.number}`;
98+
setChatInputValue(command);
99+
setFocusTrigger(prev => prev + 1); // Trigger focus
100+
}, []);
101+
102+
const handlePopulateRemoteInput = useCallback((issue: any, event: React.MouseEvent) => {
103+
event.stopPropagation(); // Prevent triggering the issue click
104+
const command = `@copilot start work on #${issue.number}`;
105+
setChatInputValue(command);
106+
setFocusTrigger(prev => prev + 1); // Trigger focus
99107
}, []);
100108

101109
const handlePullRequestClick = useCallback((pullRequest: { number: number; title: string; url: string }) => {
@@ -203,7 +211,13 @@ function Dashboard() {
203211
{/* Input Area */}
204212
<div className="input-area">
205213
<h2 className="area-header new-task">Start new task</h2>
206-
<ChatInput data={dashboardState} isGlobal={!!isGlobal} />
214+
<ChatInput
215+
data={dashboardState}
216+
isGlobal={!!isGlobal}
217+
value={chatInputValue}
218+
onValueChange={setChatInputValue}
219+
focusTrigger={focusTrigger}
220+
/>
207221

208222
</div>
209223

@@ -247,7 +261,8 @@ function Dashboard() {
247261
key={issue.number}
248262
issue={issue}
249263
onIssueClick={handleIssueClick}
250-
onStartRemoteAgent={handleStartRemoteAgent}
264+
onPopulateLocalInput={handlePopulateLocalInput}
265+
onPopulateRemoteInput={handlePopulateRemoteInput}
251266
associatedSession={associatedSession}
252267
onSessionClick={handleSessionClick}
253268
onPullRequestClick={handlePullRequestClick}

webviews/dashboardView/components/ChatInput.tsx

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,36 @@ function setupMonaco() {
143143
interface ChatInputProps {
144144
isGlobal: boolean;
145145
readonly data: DashboardState | null;
146+
value: string;
147+
onValueChange: (value: string) => void;
148+
focusTrigger?: number; // Increment this to trigger focus
146149
}
147150

148-
export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
149-
const [chatInput, setChatInput] = useState('');
151+
export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal, value, onValueChange, focusTrigger }) => {
150152
const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
151153
const [showDropdown, setShowDropdown] = useState(false);
152154

153-
// Handle content changes
154-
const handleEditorChange = useCallback((value: string | undefined) => {
155-
setChatInput(value || '');
156-
}, []);
155+
// Focus the editor when focusTrigger changes
156+
useEffect(() => {
157+
if (focusTrigger !== undefined && editor) {
158+
editor.focus();
159+
// Position cursor at the end
160+
const model = editor.getModel();
161+
if (model) {
162+
const position = model.getPositionAt(value.length);
163+
editor.setPosition(position);
164+
}
165+
}
166+
}, [focusTrigger, editor, value]);
167+
168+
// Handle content changes from the editor
169+
const handleEditorChange = useCallback((newValue: string | undefined) => {
170+
onValueChange(newValue || '');
171+
}, [onValueChange]);
157172

158173
const handleAgentClick = useCallback((agent: string) => {
159174
let finalInput: string;
160-
const currentInput = chatInput.trim();
175+
const currentInput = value.trim();
161176

162177
if (!currentInput) {
163178
// Empty input - just set the agent
@@ -174,7 +189,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
174189
}
175190
}
176191

177-
setChatInput(finalInput);
192+
onValueChange(finalInput);
178193
if (editor) {
179194
editor.focus();
180195
// Position cursor at the end
@@ -184,28 +199,28 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
184199
editor.setPosition(position);
185200
}
186201
}
187-
}, [chatInput, editor]);
202+
}, [value, editor, onValueChange]);
188203

189204
const handleSendChat = useCallback(() => {
190-
if (chatInput.trim()) {
191-
const trimmedInput = chatInput.trim();
205+
if (value.trim()) {
206+
const trimmedInput = value.trim();
192207

193208
// Send all chat input to the provider for processing
194209
vscode.postMessage({
195210
command: 'submit-chat',
196211
args: { query: trimmedInput }
197212
});
198213

199-
setChatInput('');
214+
onValueChange('');
200215
}
201-
}, [chatInput]);
216+
}, [value, onValueChange]);
202217

203218

204219

205220
// Handle dropdown option for planning task with local agent
206221
const handlePlanWithLocalAgent = useCallback(() => {
207-
if (chatInput.trim()) {
208-
const trimmedInput = chatInput.trim();
222+
if (value.trim()) {
223+
const trimmedInput = value.trim();
209224
// Remove @copilot prefix for planning with local agent
210225
const cleanQuery = trimmedInput.replace(/@copilot\s*/, '').trim();
211226

@@ -215,10 +230,10 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
215230
args: { query: cleanQuery }
216231
});
217232

218-
setChatInput('');
233+
onValueChange('');
219234
setShowDropdown(false);
220235
}
221-
}, [chatInput]);
236+
}, [value, onValueChange]);
222237

223238
// Handle clicking outside dropdown to close it
224239
useEffect(() => {
@@ -287,7 +302,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
287302
<Editor
288303
key="task-input-editor"
289304
defaultLanguage="taskInput"
290-
value={chatInput}
305+
value={value}
291306
theme="taskInputTheme"
292307
loading={null}
293308
onMount={handleEditorDidMount}
@@ -322,12 +337,12 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
322337
automaticLayout: true
323338
}}
324339
/>
325-
{isCopilotCommand(chatInput) ? (
340+
{isCopilotCommand(value) ? (
326341
<div className="send-button-container">
327342
<button
328343
className="send-button-inline split-left"
329344
onClick={handleSendChat}
330-
disabled={!chatInput.trim()}
345+
disabled={!value.trim()}
331346
title="Start new remote Copilot task (Ctrl+Enter)"
332347
>
333348
<span style={{ marginRight: '4px', fontSize: '12px' }}>Start remote task</span>
@@ -339,7 +354,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
339354
e.stopPropagation();
340355
setShowDropdown(!showDropdown);
341356
}}
342-
disabled={!chatInput.trim()}
357+
disabled={!value.trim()}
343358
title="More options"
344359
>
345360
<span className="codicon codicon-chevron-down"></span>
@@ -360,15 +375,15 @@ export const ChatInput: React.FC<ChatInputProps> = ({ data, isGlobal }) => {
360375
<button
361376
className="send-button-inline"
362377
onClick={handleSendChat}
363-
disabled={!chatInput.trim()}
378+
disabled={!value.trim()}
364379
title={
365-
isLocalCommand(chatInput)
380+
isLocalCommand(value)
366381
? 'Start new local task (Ctrl+Enter)'
367382
: 'Send message (Ctrl+Enter)'
368383
}
369384
>
370385
<span style={{ marginRight: '4px', fontSize: '12px' }}>
371-
{isLocalCommand(chatInput)
386+
{isLocalCommand(value)
372387
? 'Start local task'
373388
: 'Send'
374389
}

webviews/dashboardView/components/IssueItem.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { formatDate, formatFullDateTime, IssueData, SessionData } from '../types
99
interface IssueItemProps {
1010
issue: IssueData;
1111
onIssueClick: (issueUrl: string) => void;
12-
onStartRemoteAgent: (issue: IssueData, event: React.MouseEvent) => void;
12+
onPopulateLocalInput: (issue: IssueData, event: React.MouseEvent) => void;
13+
onPopulateRemoteInput: (issue: IssueData, event: React.MouseEvent) => void;
1314
associatedSession?: SessionData;
1415
onSessionClick?: (session: SessionData) => void;
1516
onPullRequestClick?: (pullRequest: { number: number; title: string; url: string }) => void;
@@ -20,7 +21,8 @@ interface IssueItemProps {
2021
export const IssueItem: React.FC<IssueItemProps> = ({
2122
issue,
2223
onIssueClick,
23-
onStartRemoteAgent,
24+
onPopulateLocalInput,
25+
onPopulateRemoteInput,
2426
associatedSession,
2527
onSessionClick,
2628
onPullRequestClick,
@@ -72,13 +74,24 @@ export const IssueItem: React.FC<IssueItemProps> = ({
7274
)}
7375
</div>
7476
) : (
75-
<button
76-
className="remote-agent-button"
77-
onClick={(e) => onStartRemoteAgent(issue, e)}
78-
title="Start remote agent for this issue"
79-
>
80-
<span className="codicon codicon-send-to-remote-agent"></span>
81-
</button>
77+
<div className="task-buttons">
78+
<button
79+
className="local-task-button"
80+
onClick={(e) => onPopulateLocalInput(issue, e)}
81+
title="Populate input with local task command"
82+
>
83+
<span className="codicon codicon-repo-clone"></span>
84+
<span>Local</span>
85+
</button>
86+
<button
87+
className="coding-agent-task-button"
88+
onClick={(e) => onPopulateRemoteInput(issue, e)}
89+
title="Populate input with remote agent command"
90+
>
91+
<span className="codicon codicon-robot"></span>
92+
<span>Remote</span>
93+
</button>
94+
</div>
8295
)}
8396
</div>
8497
<div className="item-metadata">

webviews/dashboardView/index.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,54 @@ body {
363363
font-size: 14px;
364364
}
365365

366+
.task-buttons {
367+
display: flex;
368+
align-items: center;
369+
gap: 4px;
370+
margin-left: 8px;
371+
flex-shrink: 0;
372+
opacity: 0;
373+
transition: opacity 0.2s;
374+
}
375+
376+
.issue-item:hover .task-buttons {
377+
opacity: 1;
378+
}
379+
380+
.local-task-button,
381+
.coding-agent-task-button {
382+
background: transparent;
383+
border: 1px solid var(--vscode-button-border);
384+
color: var(--vscode-button-foreground);
385+
cursor: pointer;
386+
padding: 3px 6px;
387+
border-radius: 3px;
388+
display: flex;
389+
align-items: center;
390+
justify-content: center;
391+
gap: 3px;
392+
font-size: 11px;
393+
transition: background-color 0.2s, border-color 0.2s;
394+
white-space: nowrap;
395+
}
396+
397+
.local-task-button:hover {
398+
background-color: var(--vscode-button-hoverBackground);
399+
border-color: var(--vscode-button-hoverBackground);
400+
color: var(--vscode-button-foreground);
401+
}
402+
403+
.coding-agent-task-button:hover {
404+
background-color: var(--vscode-button-hoverBackground);
405+
border-color: var(--vscode-button-hoverBackground);
406+
color: var(--vscode-button-foreground);
407+
}
408+
409+
.local-task-button .codicon,
410+
.coding-agent-task-button .codicon {
411+
font-size: 12px;
412+
}
413+
366414
.associated-session-info {
367415
display: flex;
368416
align-items: center;

0 commit comments

Comments
 (0)