Skip to content

Commit 27899f3

Browse files
committed
feat(vscode): integrate mpp-core CompletionManager for auto-completion
- Use mpp-core's JsCompletionManager for DevIn command completions - Add getCompletions and applyCompletion message handlers - Update CompletionPopup to support mpp-core CompletionItem format - Fix popup positioning by removing overflow:hidden from container - Add completion state management in App.tsx
1 parent d13ce4e commit 27899f3

File tree

15 files changed

+1984
-101
lines changed

15 files changed

+1984
-101
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: 48 additions & 39 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,37 +59,13 @@
5659
display: flex;
5760
gap: 8px;
5861
align-items: flex-end;
62+
padding: 8px;
5963
}
6064

61-
.chat-textarea {
65+
.input-with-completion {
66+
position: relative;
6267
flex: 1;
63-
padding: 10px 12px;
64-
border: 1px solid var(--input-border);
65-
background: var(--input-background);
66-
color: var(--input-foreground);
67-
border-radius: 6px;
68-
resize: none;
69-
font-family: inherit;
70-
font-size: inherit;
71-
line-height: 1.4;
72-
min-height: 40px;
73-
max-height: 150px;
74-
outline: none;
75-
transition: border-color 0.2s;
76-
}
77-
78-
.chat-textarea:focus {
79-
border-color: var(--accent);
80-
}
81-
82-
.chat-textarea:disabled {
83-
opacity: 0.6;
84-
cursor: not-allowed;
85-
}
86-
87-
.chat-textarea::placeholder {
88-
color: var(--foreground);
89-
opacity: 0.5;
68+
min-width: 0;
9069
}
9170

9271
.input-actions {
@@ -149,17 +128,47 @@
149128
}
150129

151130
.input-hint {
152-
margin-top: 6px;
153-
font-size: 11px;
131+
font-size: 10px;
154132
color: var(--foreground);
155-
opacity: 0.5;
133+
opacity: 0.4;
134+
white-space: nowrap;
156135
}
157136

158137
.input-hint kbd {
159-
background: var(--selection-background);
160-
padding: 2px 5px;
161-
border-radius: 3px;
138+
background: var(--background);
139+
padding: 1px 4px;
140+
border-radius: 2px;
162141
font-family: inherit;
142+
font-size: 9px;
143+
}
144+
145+
/* Token indicator */
146+
.token-indicator {
147+
font-size: 11px;
148+
color: var(--foreground);
149+
opacity: 0.6;
150+
padding: 2px 6px;
151+
background: var(--background);
152+
border-radius: 4px;
153+
}
154+
155+
/* Enhance button */
156+
.enhance-button {
157+
display: flex;
158+
align-items: center;
159+
gap: 4px;
160+
}
161+
162+
.enhance-button.enhancing {
163+
color: var(--vscode-textLink-foreground, #3794ff);
164+
animation: pulse 1.5s ease-in-out infinite;
165+
}
166+
167+
.enhance-button .enhancing-text {
163168
font-size: 10px;
164169
}
165170

171+
@keyframes pulse {
172+
0%, 100% { opacity: 0.6; }
173+
50% { opacity: 1; }
174+
}

0 commit comments

Comments
 (0)