Skip to content

Commit 40c4593

Browse files
authored
feat(vscode): Enhanced Chat Input with DevIn Language Support (#34)
feat(vscode): Enhanced Chat Input with DevIn Language Support
2 parents 2dd0fd8 + 31fb79f commit 40c4593

File tree

15 files changed

+1987
-73
lines changed

15 files changed

+1987
-73
lines changed

mpp-vscode/src/providers/chat-view.ts

Lines changed: 401 additions & 4 deletions
Large diffs are not rendered by default.

mpp-vscode/webview/src/App.tsx

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import React, { useState, useEffect, useCallback } from 'react';
99
import { Timeline } from './components/Timeline';
1010
import { ChatInput } from './components/ChatInput';
1111
import { ModelConfig } from './components/ModelSelector';
12+
import { SelectedFile } from './components/FileChip';
13+
import { CompletionItem } from './components/CompletionPopup';
1214
import { useVSCode, ExtensionMessage } from './hooks/useVSCode';
1315
import type { AgentState, ToolCallInfo, TerminalOutput, ToolCallTimelineItem } from './types/timeline';
1416
import './App.css';
@@ -18,6 +20,12 @@ interface ConfigState {
1820
currentConfigName: string | null;
1921
}
2022

23+
interface CompletionResult {
24+
newText: string;
25+
newCursorPosition: number;
26+
shouldTriggerNextCompletion: boolean;
27+
}
28+
2129
const App: React.FC = () => {
2230
// Agent state - mirrors ComposeRenderer's state
2331
const [agentState, setAgentState] = useState<AgentState>({
@@ -35,6 +43,16 @@ const App: React.FC = () => {
3543
currentConfigName: null
3644
});
3745

46+
// Token usage state
47+
const [totalTokens, setTotalTokens] = useState<number | null>(null);
48+
49+
// Active file state (for auto-add current file feature)
50+
const [activeFile, setActiveFile] = useState<SelectedFile | null>(null);
51+
52+
// Completion state - from mpp-core
53+
const [completionItems, setCompletionItems] = useState<CompletionItem[]>([]);
54+
const [completionResult, setCompletionResult] = useState<CompletionResult | null>(null);
55+
3856
const { postMessage, onMessage, isVSCode } = useVSCode();
3957

4058
// Handle messages from extension
@@ -198,6 +216,43 @@ const App: React.FC = () => {
198216
});
199217
}
200218
break;
219+
220+
// Token usage update
221+
case 'tokenUpdate':
222+
if (msg.data?.totalTokens != null) {
223+
setTotalTokens(msg.data.totalTokens as number);
224+
}
225+
break;
226+
227+
// Active file changed (for auto-add current file)
228+
case 'activeFileChanged':
229+
if (msg.data) {
230+
setActiveFile({
231+
path: msg.data.path as string,
232+
name: msg.data.name as string,
233+
relativePath: msg.data.path as string,
234+
isDirectory: msg.data.isDirectory as boolean || false
235+
});
236+
}
237+
break;
238+
239+
// Completion results from mpp-core
240+
case 'completionsResult':
241+
if (msg.data?.items) {
242+
setCompletionItems(msg.data.items as CompletionItem[]);
243+
}
244+
break;
245+
246+
// Completion applied result
247+
case 'completionApplied':
248+
if (msg.data) {
249+
setCompletionResult({
250+
newText: msg.data.newText as string,
251+
newCursorPosition: msg.data.newCursorPosition as number,
252+
shouldTriggerNextCompletion: msg.data.shouldTriggerNextCompletion as boolean
253+
});
254+
}
255+
break;
201256
}
202257
}, []);
203258

@@ -206,21 +261,35 @@ const App: React.FC = () => {
206261
return onMessage(handleExtensionMessage);
207262
}, [onMessage, handleExtensionMessage]);
208263

264+
// Request config on mount
265+
useEffect(() => {
266+
postMessage({ type: 'requestConfig' });
267+
}, [postMessage]);
268+
209269
// Send message to extension
210-
const handleSend = useCallback((content: string) => {
270+
const handleSend = useCallback((content: string, files?: SelectedFile[]) => {
271+
// Build message with file context (DevIns format)
272+
let fullContent = content;
273+
if (files && files.length > 0) {
274+
const fileCommands = files.map(f =>
275+
f.isDirectory ? `/dir:${f.relativePath}` : `/file:${f.relativePath}`
276+
).join('\n');
277+
fullContent = `${fileCommands}\n\n${content}`;
278+
}
279+
211280
// Immediately show user message in timeline for feedback
212281
setAgentState(prev => ({
213282
...prev,
214283
isProcessing: true,
215284
timeline: [...prev.timeline, {
216285
type: 'message',
217286
timestamp: Date.now(),
218-
message: { role: 'user', content }
287+
message: { role: 'user', content: fullContent }
219288
}]
220289
}));
221290

222291
// Send to extension
223-
postMessage({ type: 'sendMessage', content });
292+
postMessage({ type: 'sendMessage', content: fullContent });
224293
}, [postMessage]);
225294

226295
// Clear history
@@ -248,6 +317,48 @@ const App: React.FC = () => {
248317
postMessage({ type: 'selectConfig', data: { configName: config.name } });
249318
}, [postMessage]);
250319

320+
// Handle prompt optimization
321+
const handlePromptOptimize = useCallback(async (prompt: string): Promise<string> => {
322+
return new Promise((resolve) => {
323+
// Send optimization request to extension
324+
postMessage({ type: 'action', action: 'optimizePrompt', data: { prompt } });
325+
326+
// Listen for response
327+
const handler = (event: MessageEvent) => {
328+
const msg = event.data;
329+
if (msg.type === 'promptOptimized' && msg.data?.optimizedPrompt) {
330+
window.removeEventListener('message', handler);
331+
resolve(msg.data.optimizedPrompt as string);
332+
} else if (msg.type === 'promptOptimizeFailed') {
333+
window.removeEventListener('message', handler);
334+
resolve(prompt); // Return original on failure
335+
}
336+
};
337+
window.addEventListener('message', handler);
338+
339+
// Timeout after 30 seconds
340+
setTimeout(() => {
341+
window.removeEventListener('message', handler);
342+
resolve(prompt);
343+
}, 30000);
344+
});
345+
}, [postMessage]);
346+
347+
// Handle MCP config click
348+
const handleMcpConfigClick = useCallback(() => {
349+
postMessage({ type: 'action', action: 'openMcpConfig' });
350+
}, [postMessage]);
351+
352+
// Handle get completions from mpp-core
353+
const handleGetCompletions = useCallback((text: string, cursorPosition: number) => {
354+
postMessage({ type: 'getCompletions', data: { text, cursorPosition } });
355+
}, [postMessage]);
356+
357+
// Handle apply completion from mpp-core
358+
const handleApplyCompletion = useCallback((text: string, cursorPosition: number, completionIndex: number) => {
359+
postMessage({ type: 'applyCompletion', data: { text, cursorPosition, completionIndex } });
360+
}, [postMessage]);
361+
251362
// Check if we need to show config prompt
252363
const needsConfig = agentState.timeline.length === 0 &&
253364
agentState.currentStreamingContent.includes('No configuration found') ||
@@ -310,11 +421,19 @@ const App: React.FC = () => {
310421
onStop={handleStop}
311422
onConfigSelect={handleConfigSelect}
312423
onConfigureClick={handleOpenConfig}
424+
onMcpConfigClick={handleMcpConfigClick}
425+
onPromptOptimize={handlePromptOptimize}
426+
onGetCompletions={handleGetCompletions}
427+
onApplyCompletion={handleApplyCompletion}
428+
completionItems={completionItems}
429+
completionResult={completionResult}
313430
disabled={agentState.isProcessing}
314431
isExecuting={agentState.isProcessing}
315432
placeholder="Ask AutoDev anything... (use / for commands, @ for agents)"
316433
availableConfigs={configState.availableConfigs}
317434
currentConfigName={configState.currentConfigName}
435+
totalTokens={totalTokens}
436+
activeFile={activeFile}
318437
/>
319438
</div>
320439
);

mpp-vscode/webview/src/components/ChatInput.css

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
.chat-input-container {
2-
padding: 8px 12px 12px;
3-
border-top: 1px solid var(--panel-border);
2+
position: relative;
3+
border: 1px solid var(--panel-border);
4+
border-radius: 8px;
45
background: var(--background);
6+
margin: 8px;
57
}
68

7-
/* Toolbar */
9+
/* Bottom Toolbar */
810
.input-toolbar {
911
display: flex;
1012
justify-content: space-between;
1113
align-items: center;
12-
margin-bottom: 8px;
13-
padding: 0 4px;
14+
padding: 6px 8px;
15+
border-top: 1px solid var(--panel-border);
16+
background: var(--selection-background);
1417
}
1518

1619
.toolbar-left {
@@ -56,6 +59,13 @@
5659
display: flex;
5760
gap: 8px;
5861
align-items: flex-end;
62+
padding: 8px;
63+
}
64+
65+
.input-with-completion {
66+
position: relative;
67+
flex: 1;
68+
min-width: 0;
5969
}
6070

6171
.chat-textarea {
@@ -149,17 +159,47 @@
149159
}
150160

151161
.input-hint {
152-
margin-top: 6px;
153-
font-size: 11px;
162+
font-size: 10px;
154163
color: var(--foreground);
155-
opacity: 0.5;
164+
opacity: 0.4;
165+
white-space: nowrap;
156166
}
157167

158168
.input-hint kbd {
159-
background: var(--selection-background);
160-
padding: 2px 5px;
161-
border-radius: 3px;
169+
background: var(--background);
170+
padding: 1px 4px;
171+
border-radius: 2px;
162172
font-family: inherit;
173+
font-size: 9px;
174+
}
175+
176+
/* Token indicator */
177+
.token-indicator {
178+
font-size: 11px;
179+
color: var(--foreground);
180+
opacity: 0.6;
181+
padding: 2px 6px;
182+
background: var(--background);
183+
border-radius: 4px;
184+
}
185+
186+
/* Enhance button */
187+
.enhance-button {
188+
display: flex;
189+
align-items: center;
190+
gap: 4px;
191+
}
192+
193+
.enhance-button.enhancing {
194+
color: var(--vscode-textLink-foreground, #3794ff);
195+
animation: pulse 1.5s ease-in-out infinite;
196+
}
197+
198+
.enhance-button .enhancing-text {
163199
font-size: 10px;
164200
}
165201

202+
@keyframes pulse {
203+
0%, 100% { opacity: 0.6; }
204+
50% { opacity: 1; }
205+
}

0 commit comments

Comments
 (0)