Просмотр исходного кода

Refactor to support more sections in the future

Matt Rubens 11 месяцев назад
Родитель
Сommit
092a121a37

+ 3 - 3
src/core/prompts/architect.ts

@@ -1,4 +1,4 @@
-import { architectMode, defaultPrompts } from "../../shared/modes"
+import { architectMode, defaultPrompts, PromptComponent } from "../../shared/modes"
 import { getToolDescriptionsForMode } from "./tools"
 import {
     getRulesSection,
@@ -20,8 +20,8 @@ export const ARCHITECT_PROMPT = async (
     mcpHub?: McpHub,
     diffStrategy?: DiffStrategy,
     browserViewportSize?: string,
-    customPrompt?: string,
-) => `${customPrompt || defaultPrompts[architectMode]}
+    customPrompt?: PromptComponent,
+) => `${customPrompt?.roleDefinition || defaultPrompts[architectMode].roleDefinition}
 
 ${getSharedToolUseSection()}
 

+ 3 - 3
src/core/prompts/ask.ts

@@ -1,4 +1,4 @@
-import { Mode, askMode, defaultPrompts } from "../../shared/modes"
+import { Mode, askMode, defaultPrompts, PromptComponent } from "../../shared/modes"
 import { getToolDescriptionsForMode } from "./tools"
 import {
     getRulesSection,
@@ -21,8 +21,8 @@ export const ASK_PROMPT = async (
     mcpHub?: McpHub,
     diffStrategy?: DiffStrategy,
     browserViewportSize?: string,
-    customPrompt?: string,
-) => `${customPrompt || defaultPrompts[askMode]}
+    customPrompt?: PromptComponent,
+) => `${customPrompt?.roleDefinition || defaultPrompts[askMode].roleDefinition}
 
 ${getSharedToolUseSection()}
 

+ 3 - 3
src/core/prompts/code.ts

@@ -1,4 +1,4 @@
-import { Mode, codeMode, defaultPrompts } from "../../shared/modes"
+import { Mode, codeMode, defaultPrompts, PromptComponent } from "../../shared/modes"
 import { getToolDescriptionsForMode } from "./tools"
 import {
     getRulesSection,
@@ -21,8 +21,8 @@ export const CODE_PROMPT = async (
     mcpHub?: McpHub,
     diffStrategy?: DiffStrategy,
     browserViewportSize?: string,
-    customPrompt?: string,
-) => `${customPrompt || defaultPrompts[codeMode]}
+    customPrompt?: PromptComponent,
+) => `${customPrompt?.roleDefinition || defaultPrompts[codeMode].roleDefinition}
 
 ${getSharedToolUseSection()}
 

+ 2 - 1
src/core/prompts/system.ts

@@ -4,6 +4,7 @@ import { CODE_PROMPT } from "./code"
 import { ARCHITECT_PROMPT } from "./architect"
 import { ASK_PROMPT } from "./ask"
 import { Mode, codeMode, architectMode, askMode } from "./modes"
+import { CustomPrompts } from "../../shared/modes"
 import fs from 'fs/promises'
 import path from 'path'
 
@@ -64,7 +65,7 @@ export const SYSTEM_PROMPT = async (
 	diffStrategy?: DiffStrategy,
 	browserViewportSize?: string,
     mode: Mode = codeMode,
-    customPrompts?: { ask?: string; code?: string; architect?: string; enhance?: string },
+    customPrompts?: CustomPrompts,
 ) => {
     switch (mode) {
         case architectMode:

+ 28 - 2
src/core/webview/ClineProvider.ts

@@ -16,7 +16,7 @@ import { ApiConfiguration, ApiProvider, ModelInfo } from "../../shared/api"
 import { findLast } from "../../shared/array"
 import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
 import { HistoryItem } from "../../shared/HistoryItem"
-import { WebviewMessage } from "../../shared/WebviewMessage"
+import { WebviewMessage, PromptMode } from "../../shared/WebviewMessage"
 import { defaultPrompts } from "../../shared/modes"
 import { SYSTEM_PROMPT, addCustomInstructions } from "../prompts/system"
 import { fileExistsAtPath } from "../../utils/fs"
@@ -731,6 +731,32 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						
 						await this.postStateToWebview()
 						break
+					case "updateEnhancedPrompt":
+						if (message.text !== undefined) {
+							const existingPrompts = await this.getGlobalState("customPrompts") || {}
+							
+							const updatedPrompts = {
+								...existingPrompts,
+								enhance: message.text
+							}
+							
+							await this.updateGlobalState("customPrompts", updatedPrompts)
+							
+							// Get current state and explicitly include customPrompts
+							const currentState = await this.getState()
+							
+							const stateWithPrompts = {
+								...currentState,
+								customPrompts: updatedPrompts
+							}
+							
+							// Post state with prompts
+							this.view?.webview.postMessage({
+								type: "state",
+								state: stateWithPrompts
+							})
+						}
+						break
 					case "updatePrompt":
 						if (message.promptMode && message.customPrompt !== undefined) {
 							const existingPrompts = await this.getGlobalState("customPrompts") || {}
@@ -866,7 +892,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						try {
 							const { apiConfiguration, customPrompts, customInstructions, preferredLanguage, browserViewportSize, mcpEnabled } = await this.getState()
 							const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) || ''
-							
+
 							const fullPrompt = await SYSTEM_PROMPT(
 								cwd,
 								apiConfiguration.openRouterModelInfo?.supportsComputerUse ?? false,

+ 0 - 12
src/core/webview/__tests__/ClineProvider.test.ts

@@ -838,18 +838,6 @@ describe('ClineProvider', () => {
             );
         });
 
-        test('returns empty prompt for enhance mode', async () => {
-            const enhanceHandler = getMessageHandler();
-            await enhanceHandler({ type: 'getSystemPrompt', mode: 'enhance' })
-
-            expect(mockPostMessage).toHaveBeenCalledWith(
-                expect.objectContaining({
-                    type: 'systemPrompt',
-                    text: ''
-                })
-            )
-        })
-
         test('handles errors gracefully', async () => {
             // Mock SYSTEM_PROMPT to throw an error
             const systemPrompt = require('../../prompts/system')

+ 1 - 1
src/shared/ExtensionMessage.ts

@@ -48,7 +48,7 @@ export interface ExtensionMessage {
 	mcpServers?: McpServer[]
 	commits?: GitCommit[]
 	listApiConfig?: ApiConfigMeta[]
-	mode?: Mode | 'enhance'
+	mode?: Mode
 }
 
 export interface ApiConfigMeta {

+ 3 - 2
src/shared/WebviewMessage.ts

@@ -1,5 +1,5 @@
 import { ApiConfiguration, ApiProvider } from "./api"
-import { Mode } from "./modes"
+import { Mode, PromptComponent } from "./modes"
 
 export type PromptMode = Mode | 'enhance'
 
@@ -66,6 +66,7 @@ export interface WebviewMessage {
 		| "setApiConfigPassword"
 		| "mode"
 		| "updatePrompt"
+		| "updateEnhancedPrompt"
 		| "getSystemPrompt"
 		| "systemPrompt"
 		| "enhancementApiConfigId"
@@ -83,7 +84,7 @@ export interface WebviewMessage {
 	alwaysAllow?: boolean
 	mode?: Mode
 	promptMode?: PromptMode
-	customPrompt?: string
+	customPrompt?: PromptComponent
 	dataUrls?: string[]
 	values?: Record<string, any>
 	query?: string

+ 16 - 6
src/shared/modes.ts

@@ -4,16 +4,26 @@ export const askMode = 'ask' as const;
 
 export type Mode = typeof codeMode | typeof architectMode | typeof askMode;
 
+export type PromptComponent = {
+    roleDefinition?: string;
+}
+
 export type CustomPrompts = {
-    ask?: string;
-    code?: string;
-    architect?: string;
+    ask?: PromptComponent;
+    code?: PromptComponent;
+    architect?: PromptComponent;
     enhance?: string;
 }
 
 export const defaultPrompts = {
-    [askMode]: "You are Cline, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics. You can analyze code, explain concepts, and access external resources while maintaining a read-only approach to the codebase. Make sure to answer the user's questions and don't rush to switch to implementing code.",
-    [codeMode]: "You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
-    [architectMode]: "You are Cline, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements while maintaining a read-only approach to the codebase. Make sure to help the user come up with a solid implementation plan for their project and don't rush to switch to implementing code.",
+    [askMode]: {
+        roleDefinition: "You are Cline, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics. You can analyze code, explain concepts, and access external resources while maintaining a read-only approach to the codebase. Make sure to answer the user's questions and don't rush to switch to implementing code.",
+    },
+    [codeMode]: {
+        roleDefinition: "You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
+    },
+    [architectMode]: {
+        roleDefinition: "You are Cline, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements while maintaining a read-only approach to the codebase. Make sure to help the user come up with a solid implementation plan for their project and don't rush to switch to implementing code.",
+    },
     enhance: "Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):"
 } as const;

+ 53 - 57
webview-ui/src/components/prompts/PromptsView.tsx

@@ -1,6 +1,6 @@
 import { VSCodeButton, VSCodeTextArea, VSCodeDropdown, VSCodeOption, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"
 import { useExtensionState } from "../../context/ExtensionStateContext"
-import { defaultPrompts, askMode, codeMode, architectMode, Mode } from "../../../../src/shared/modes"
+import { defaultPrompts, askMode, codeMode, architectMode, Mode, PromptComponent } from "../../../../src/shared/modes"
 import { vscode } from "../../utils/vscode"
 import React, { useState, useEffect } from "react"
 
@@ -8,6 +8,12 @@ type PromptsViewProps = {
 	onDone: () => void
 }
 
+const AGENT_MODES = [
+	{ id: codeMode, label: 'Code' },
+	{ id: architectMode, label: 'Architect' },
+	{ id: askMode, label: 'Ask' },
+] as const
+
 const PromptsView = ({ onDone }: PromptsViewProps) => {
 	const { customPrompts, listApiConfigMeta, enhancementApiConfigId, setEnhancementApiConfigId, mode } = useExtensionState()
 	const [testPrompt, setTestPrompt] = useState('')
@@ -38,31 +44,47 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 		return () => window.removeEventListener('message', handler)
 	}, [])
 
-	type PromptMode = keyof typeof defaultPrompts
+	type AgentMode = typeof codeMode | typeof architectMode | typeof askMode
 
-	const updatePromptValue = (promptMode: PromptMode, value: string) => {
+	const updateAgentPrompt = (mode: AgentMode, promptData: PromptComponent) => {
 		vscode.postMessage({
 			type: "updatePrompt",
-			promptMode,
-			customPrompt: value
+			promptMode: mode,
+			customPrompt: promptData
 		})
 	}
 
-	const handlePromptChange = (mode: PromptMode, e: Event | React.FormEvent<HTMLElement>) => {
+	const updateEnhancePrompt = (value: string | undefined) => {
+		vscode.postMessage({
+			type: "updateEnhancedPrompt",
+			text: value
+		})
+	}
+
+	const handleAgentPromptChange = (mode: AgentMode, e: Event | React.FormEvent<HTMLElement>) => {
 		const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
-		updatePromptValue(mode, value)
+		updateAgentPrompt(mode, { roleDefinition: value.trim() || undefined })
 	}
 
-	const handleReset = (mode: PromptMode) => {
-		const defaultValue = defaultPrompts[mode]
-		updatePromptValue(mode, defaultValue)
+	const handleEnhancePromptChange = (e: Event | React.FormEvent<HTMLElement>) => {
+		const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
+		updateEnhancePrompt(value.trim() || undefined)
 	}
 
-	const getPromptValue = (mode: PromptMode): string => {
-		if (mode === 'enhance') {
-			return customPrompts?.enhance ?? defaultPrompts.enhance
-		}
-		return customPrompts?.[mode] ?? defaultPrompts[mode]
+	const handleAgentReset = (mode: AgentMode) => {
+		updateAgentPrompt(mode, { roleDefinition: undefined })
+	}
+
+	const handleEnhanceReset = () => {
+		updateEnhancePrompt(undefined)
+	}
+
+	const getAgentPromptValue = (mode: AgentMode): string => {
+		return customPrompts?.[mode]?.roleDefinition ?? defaultPrompts[mode].roleDefinition
+	}
+
+	const getEnhancePromptValue = (): string => {
+		return customPrompts?.enhance ?? defaultPrompts.enhance
 	}
 
 	const handleTestEnhancement = () => {
@@ -117,11 +139,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 					marginBottom: '12px'
 				}}>
 					<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
-						{[
-							{ id: codeMode, label: 'Code' },
-							{ id: architectMode, label: 'Architect' },
-							{ id: askMode, label: 'Ask' },
-						].map((tab, index) => (
+						{AGENT_MODES.map((tab, index) => (
 							<React.Fragment key={tab.id}>
 								<button
 									data-testid={`${tab.id}-tab`}
@@ -142,7 +160,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 								>
 									{tab.label}
 								</button>
-								{index < 2 && (
+								{index < AGENT_MODES.length - 1 && (
 									<span style={{ color: 'var(--vscode-foreground)', opacity: 0.4 }}>|</span>
 								)}
 							</React.Fragment>
@@ -150,7 +168,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 					</div>
 					<VSCodeButton
 						appearance="icon"
-						onClick={() => handleReset(activeTab as any)}
+						onClick={() => handleAgentReset(activeTab)}
 						data-testid="reset-prompt-button"
 						title="Revert to default"
 					>
@@ -159,38 +177,16 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 				</div>
 
 				<div style={{ marginBottom: '8px' }}>
-					{activeTab === codeMode && (
-						<VSCodeTextArea
-							value={getPromptValue(codeMode)}
-							onChange={(e) => handlePromptChange(codeMode, e)}
-							rows={4}
-							resize="vertical"
-							style={{ width: "100%" }}
-							data-testid="code-prompt-textarea"
-						/>
-					)}					
-					{activeTab === architectMode && (
-						<VSCodeTextArea
-							value={getPromptValue(architectMode)}
-							onChange={(e) => handlePromptChange(architectMode, e)}
-							rows={4}
-							resize="vertical"
-							style={{ width: "100%" }}
-							data-testid="architect-prompt-textarea"
-						/>
-					)}
-					{activeTab === askMode && (
-						<VSCodeTextArea
-							value={getPromptValue(askMode)}
-							onChange={(e) => handlePromptChange(askMode, e)}
-							rows={4}
-							resize="vertical"
-							style={{ width: "100%" }}
-							data-testid="ask-prompt-textarea"
-						/>
-					)}
+					<VSCodeTextArea
+						value={getAgentPromptValue(activeTab)}
+						onChange={(e) => handleAgentPromptChange(activeTab, e)}
+						rows={4}
+						resize="vertical"
+						style={{ width: "100%" }}
+						data-testid={`${activeTab}-prompt-textarea`}
+					/>
 				</div>
-				<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'flex-end' }}>
+				<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'flex-start' }}>
 					<VSCodeButton
 						appearance="primary"
 						onClick={() => {
@@ -241,14 +237,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						<div style={{ marginBottom: "8px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
 							<div style={{ fontWeight: "bold" }}>Enhancement Prompt</div>
 							<div style={{ display: "flex", gap: "8px" }}>
-								<VSCodeButton appearance="icon" onClick={() => handleReset('enhance')} title="Revert to default">
+								<VSCodeButton appearance="icon" onClick={handleEnhanceReset} title="Revert to default">
 									<span className="codicon codicon-discard"></span>
 								</VSCodeButton>
 							</div>
 						</div>
 						<VSCodeTextArea
-							value={getPromptValue('enhance')}
-							onChange={(e) => handlePromptChange('enhance', e)}
+							value={getEnhancePromptValue()}
+							onChange={handleEnhancePromptChange}
 							rows={4}
 							resize="vertical"
 							style={{ width: "100%" }}
@@ -267,7 +263,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 							<div style={{ 
 								marginTop: "8px",
 								display: "flex", 
-								justifyContent: "flex-end",
+								justifyContent: "flex-start",
 								alignItems: "center", 
 								gap: 8 
 							}}>

+ 2 - 3
webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx

@@ -3,7 +3,6 @@ import '@testing-library/jest-dom'
 import PromptsView from '../PromptsView'
 import { ExtensionStateContext } from '../../../context/ExtensionStateContext'
 import { vscode } from '../../../utils/vscode'
-import { defaultPrompts } from '../../../../../src/shared/modes'
 
 // Mock vscode API
 jest.mock('../../../utils/vscode', () => ({
@@ -97,7 +96,7 @@ describe('PromptsView', () => {
     expect(vscode.postMessage).toHaveBeenCalledWith({
       type: 'updatePrompt',
       promptMode: 'code',
-      customPrompt: 'New prompt value'
+      customPrompt: { roleDefinition: 'New prompt value' }
     })
   })
 
@@ -110,7 +109,7 @@ describe('PromptsView', () => {
     expect(vscode.postMessage).toHaveBeenCalledWith({
       type: 'updatePrompt',
       promptMode: 'code',
-      customPrompt: defaultPrompts.code
+      customPrompt: { roleDefinition: undefined }
     })
   })
 

+ 2 - 2
webview-ui/src/context/ExtensionStateContext.tsx

@@ -17,7 +17,7 @@ import {
 	checkExistKey
 } from "../../../src/shared/checkExistApiConfig"
 import { Mode } from "../../../src/core/prompts/types"
-import { codeMode, CustomPrompts } from "../../../src/shared/modes"
+import { codeMode, CustomPrompts, defaultPrompts } from "../../../src/shared/modes"
 
 export interface ExtensionStateContextType extends ExtensionState {
 	didHydrateState: boolean
@@ -89,7 +89,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		currentApiConfigName: 'default',
 		listApiConfigMeta: [],
 		mode: codeMode,
-		customPrompts: {},
+		customPrompts: defaultPrompts,
 		enhancementApiConfigId: '',
 	})
 	const [didHydrateState, setDidHydrateState] = useState(false)