-
Notifications
You must be signed in to change notification settings - Fork 1
feat(vscode): Enhanced Chat Input with DevIn Language Support #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7c2bef1
0fd86e4
1a1fad2
578308f
80020cc
aad2201
4f887f9
407cb3d
2975520
98facf2
d13ce4e
27899f3
31fb79f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ import React, { useState, useEffect, useCallback } from 'react'; | |
| import { Timeline } from './components/Timeline'; | ||
| import { ChatInput } from './components/ChatInput'; | ||
| import { ModelConfig } from './components/ModelSelector'; | ||
| import { SelectedFile } from './components/FileChip'; | ||
| import { CompletionItem } from './components/CompletionPopup'; | ||
| import { useVSCode, ExtensionMessage } from './hooks/useVSCode'; | ||
| import type { AgentState, ToolCallInfo, TerminalOutput, ToolCallTimelineItem } from './types/timeline'; | ||
| import './App.css'; | ||
|
|
@@ -18,6 +20,12 @@ interface ConfigState { | |
| currentConfigName: string | null; | ||
| } | ||
|
|
||
| interface CompletionResult { | ||
| newText: string; | ||
| newCursorPosition: number; | ||
| shouldTriggerNextCompletion: boolean; | ||
| } | ||
|
|
||
| const App: React.FC = () => { | ||
| // Agent state - mirrors ComposeRenderer's state | ||
| const [agentState, setAgentState] = useState<AgentState>({ | ||
|
|
@@ -35,6 +43,16 @@ const App: React.FC = () => { | |
| currentConfigName: null | ||
| }); | ||
|
|
||
| // Token usage state | ||
| const [totalTokens, setTotalTokens] = useState<number | null>(null); | ||
|
|
||
| // Active file state (for auto-add current file feature) | ||
| const [activeFile, setActiveFile] = useState<SelectedFile | null>(null); | ||
|
|
||
| // Completion state - from mpp-core | ||
| const [completionItems, setCompletionItems] = useState<CompletionItem[]>([]); | ||
| const [completionResult, setCompletionResult] = useState<CompletionResult | null>(null); | ||
|
|
||
| const { postMessage, onMessage, isVSCode } = useVSCode(); | ||
|
|
||
| // Handle messages from extension | ||
|
|
@@ -198,6 +216,43 @@ const App: React.FC = () => { | |
| }); | ||
| } | ||
| break; | ||
|
|
||
| // Token usage update | ||
| case 'tokenUpdate': | ||
| if (msg.data?.totalTokens != null) { | ||
| setTotalTokens(msg.data.totalTokens as number); | ||
| } | ||
| break; | ||
|
|
||
| // Active file changed (for auto-add current file) | ||
| case 'activeFileChanged': | ||
| if (msg.data) { | ||
| setActiveFile({ | ||
| path: msg.data.path as string, | ||
| name: msg.data.name as string, | ||
| relativePath: msg.data.path as string, | ||
| isDirectory: msg.data.isDirectory as boolean || false | ||
| }); | ||
| } | ||
| break; | ||
|
|
||
| // Completion results from mpp-core | ||
| case 'completionsResult': | ||
| if (msg.data?.items) { | ||
| setCompletionItems(msg.data.items as CompletionItem[]); | ||
| } | ||
| break; | ||
|
|
||
| // Completion applied result | ||
| case 'completionApplied': | ||
| if (msg.data) { | ||
| setCompletionResult({ | ||
| newText: msg.data.newText as string, | ||
| newCursorPosition: msg.data.newCursorPosition as number, | ||
| shouldTriggerNextCompletion: msg.data.shouldTriggerNextCompletion as boolean | ||
| }); | ||
| } | ||
| break; | ||
| } | ||
| }, []); | ||
|
|
||
|
|
@@ -206,21 +261,35 @@ const App: React.FC = () => { | |
| return onMessage(handleExtensionMessage); | ||
| }, [onMessage, handleExtensionMessage]); | ||
|
|
||
| // Request config on mount | ||
| useEffect(() => { | ||
| postMessage({ type: 'requestConfig' }); | ||
| }, [postMessage]); | ||
|
|
||
| // Send message to extension | ||
| const handleSend = useCallback((content: string) => { | ||
| const handleSend = useCallback((content: string, files?: SelectedFile[]) => { | ||
| // Build message with file context (DevIns format) | ||
| let fullContent = content; | ||
| if (files && files.length > 0) { | ||
| const fileCommands = files.map(f => | ||
| f.isDirectory ? `/dir:${f.relativePath}` : `/file:${f.relativePath}` | ||
| ).join('\n'); | ||
| fullContent = `${fileCommands}\n\n${content}`; | ||
| } | ||
|
|
||
| // Immediately show user message in timeline for feedback | ||
| setAgentState(prev => ({ | ||
| ...prev, | ||
| isProcessing: true, | ||
| timeline: [...prev.timeline, { | ||
| type: 'message', | ||
| timestamp: Date.now(), | ||
| message: { role: 'user', content } | ||
| message: { role: 'user', content: fullContent } | ||
| }] | ||
| })); | ||
|
|
||
| // Send to extension | ||
| postMessage({ type: 'sendMessage', content }); | ||
| postMessage({ type: 'sendMessage', content: fullContent }); | ||
| }, [postMessage]); | ||
|
|
||
| // Clear history | ||
|
|
@@ -248,6 +317,48 @@ const App: React.FC = () => { | |
| postMessage({ type: 'selectConfig', data: { configName: config.name } }); | ||
| }, [postMessage]); | ||
|
|
||
| // Handle prompt optimization | ||
| const handlePromptOptimize = useCallback(async (prompt: string): Promise<string> => { | ||
| return new Promise((resolve) => { | ||
| // Send optimization request to extension | ||
| postMessage({ type: 'action', action: 'optimizePrompt', data: { prompt } }); | ||
|
|
||
| // Listen for response | ||
| const handler = (event: MessageEvent) => { | ||
| const msg = event.data; | ||
| if (msg.type === 'promptOptimized' && msg.data?.optimizedPrompt) { | ||
| window.removeEventListener('message', handler); | ||
| resolve(msg.data.optimizedPrompt as string); | ||
| } else if (msg.type === 'promptOptimizeFailed') { | ||
| window.removeEventListener('message', handler); | ||
| resolve(prompt); // Return original on failure | ||
| } | ||
| }; | ||
| window.addEventListener('message', handler); | ||
|
|
||
| // Timeout after 30 seconds | ||
| setTimeout(() => { | ||
| window.removeEventListener('message', handler); | ||
| resolve(prompt); | ||
| }, 30000); | ||
| }); | ||
| }, [postMessage]); | ||
|
Comment on lines
+320
to
+345
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix memory leak and use established message handling pattern. The Refactor to use the established pattern or manage cleanup properly: const handlePromptOptimize = useCallback(async (prompt: string): Promise<string> => {
return new Promise((resolve) => {
+ let resolved = false;
+
postMessage({ type: 'action', action: 'optimizePrompt', data: { prompt } });
const handler = (event: MessageEvent) => {
const msg = event.data;
if (msg.type === 'promptOptimized' && msg.data?.optimizedPrompt) {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(msg.data.optimizedPrompt as string);
} else if (msg.type === 'promptOptimizeFailed') {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(prompt);
}
};
window.addEventListener('message', handler);
setTimeout(() => {
+ if (resolved) return;
+ resolved = true;
window.removeEventListener('message', handler);
resolve(prompt);
}, 30000);
});
}, [postMessage]);Better yet, consider using a request ID system with the
🤖 Prompt for AI Agents |
||
|
|
||
| // Handle MCP config click | ||
| const handleMcpConfigClick = useCallback(() => { | ||
| postMessage({ type: 'action', action: 'openMcpConfig' }); | ||
| }, [postMessage]); | ||
|
|
||
| // Handle get completions from mpp-core | ||
| const handleGetCompletions = useCallback((text: string, cursorPosition: number) => { | ||
| postMessage({ type: 'getCompletions', data: { text, cursorPosition } }); | ||
| }, [postMessage]); | ||
|
|
||
| // Handle apply completion from mpp-core | ||
| const handleApplyCompletion = useCallback((text: string, cursorPosition: number, completionIndex: number) => { | ||
| postMessage({ type: 'applyCompletion', data: { text, cursorPosition, completionIndex } }); | ||
| }, [postMessage]); | ||
|
|
||
| // Check if we need to show config prompt | ||
| const needsConfig = agentState.timeline.length === 0 && | ||
| agentState.currentStreamingContent.includes('No configuration found') || | ||
|
|
@@ -310,11 +421,19 @@ const App: React.FC = () => { | |
| onStop={handleStop} | ||
| onConfigSelect={handleConfigSelect} | ||
| onConfigureClick={handleOpenConfig} | ||
| onMcpConfigClick={handleMcpConfigClick} | ||
| onPromptOptimize={handlePromptOptimize} | ||
| onGetCompletions={handleGetCompletions} | ||
| onApplyCompletion={handleApplyCompletion} | ||
| completionItems={completionItems} | ||
| completionResult={completionResult} | ||
| disabled={agentState.isProcessing} | ||
| isExecuting={agentState.isProcessing} | ||
| placeholder="Ask AutoDev anything... (use / for commands, @ for agents)" | ||
| availableConfigs={configState.availableConfigs} | ||
| currentConfigName={configState.currentConfigName} | ||
| totalTokens={totalTokens} | ||
| activeFile={activeFile} | ||
| /> | ||
| </div> | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strengthen type safety in message handling.
The message handlers use unchecked type assertions (
as string,as number,as CompletionItem[]) without validating the data structure. Ifmsg.datadoesn't match the expected shape, runtime errors could occur.Consider adding runtime validation or using type guards to verify the data before casting, especially for complex types like
CompletionItem[].Example validation pattern:
case 'completionsResult': if (msg.data?.items) { - setCompletionItems(msg.data.items as CompletionItem[]); + const items = msg.data.items; + if (Array.isArray(items)) { + setCompletionItems(items as CompletionItem[]); + } } break;📝 Committable suggestion
🤖 Prompt for AI Agents