From 092a121a37072ce9be97aa17a67e620f66f60d4a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 14 Jan 2025 07:55:16 -0500 Subject: [PATCH] Refactor to support more sections in the future --- src/core/prompts/architect.ts | 6 +- src/core/prompts/ask.ts | 6 +- src/core/prompts/code.ts | 6 +- src/core/prompts/system.ts | 3 +- src/core/webview/ClineProvider.ts | 30 ++++- .../webview/__tests__/ClineProvider.test.ts | 12 -- src/shared/ExtensionMessage.ts | 2 +- src/shared/WebviewMessage.ts | 5 +- src/shared/modes.ts | 22 +++- .../src/components/prompts/PromptsView.tsx | 110 +++++++++--------- .../prompts/__tests__/PromptsView.test.tsx | 5 +- .../src/context/ExtensionStateContext.tsx | 4 +- 12 files changed, 116 insertions(+), 95 deletions(-) diff --git a/src/core/prompts/architect.ts b/src/core/prompts/architect.ts index d0dc8c8..8bfb931 100644 --- a/src/core/prompts/architect.ts +++ b/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()} diff --git a/src/core/prompts/ask.ts b/src/core/prompts/ask.ts index 2794a72..bc86d3e 100644 --- a/src/core/prompts/ask.ts +++ b/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()} diff --git a/src/core/prompts/code.ts b/src/core/prompts/code.ts index 3bf8854..7d2a7f3 100644 --- a/src/core/prompts/code.ts +++ b/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()} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 9e61d7b..d4c2c8e 100644 --- a/src/core/prompts/system.ts +++ b/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: diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 4b0d9ea..069301b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/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, diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 1d8f21c..4a89eb3 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/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') diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 4e5624c..e09a1cc 100644 --- a/src/shared/ExtensionMessage.ts +++ b/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 { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 58c181b..5960512 100644 --- a/src/shared/WebviewMessage.ts +++ b/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 query?: string diff --git a/src/shared/modes.ts b/src/shared/modes.ts index c7dc7a7..ad0d462 100644 --- a/src/shared/modes.ts +++ b/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; \ No newline at end of file diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index af4e199..6b7bdb4 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/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) => { + const updateEnhancePrompt = (value: string | undefined) => { + vscode.postMessage({ + type: "updateEnhancedPrompt", + text: value + }) + } + + const handleAgentPromptChange = (mode: AgentMode, e: Event | React.FormEvent) => { 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) => { + 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' }}>
- {[ - { id: codeMode, label: 'Code' }, - { id: architectMode, label: 'Architect' }, - { id: askMode, label: 'Ask' }, - ].map((tab, index) => ( + {AGENT_MODES.map((tab, index) => ( - {index < 2 && ( + {index < AGENT_MODES.length - 1 && ( | )} @@ -150,7 +168,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
handleReset(activeTab as any)} + onClick={() => handleAgentReset(activeTab)} data-testid="reset-prompt-button" title="Revert to default" > @@ -159,38 +177,16 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
- {activeTab === codeMode && ( - handlePromptChange(codeMode, e)} - rows={4} - resize="vertical" - style={{ width: "100%" }} - data-testid="code-prompt-textarea" - /> - )} - {activeTab === architectMode && ( - handlePromptChange(architectMode, e)} - rows={4} - resize="vertical" - style={{ width: "100%" }} - data-testid="architect-prompt-textarea" - /> - )} - {activeTab === askMode && ( - handlePromptChange(askMode, e)} - rows={4} - resize="vertical" - style={{ width: "100%" }} - data-testid="ask-prompt-textarea" - /> - )} + handleAgentPromptChange(activeTab, e)} + rows={4} + resize="vertical" + style={{ width: "100%" }} + data-testid={`${activeTab}-prompt-textarea`} + />
-
+
{ @@ -241,14 +237,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
Enhancement Prompt
- handleReset('enhance')} title="Revert to default"> +
handlePromptChange('enhance', e)} + value={getEnhancePromptValue()} + onChange={handleEnhancePromptChange} rows={4} resize="vertical" style={{ width: "100%" }} @@ -267,7 +263,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
diff --git a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx b/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx index 530de12..311702f 100644 --- a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx +++ b/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 } }) }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 24596ca..8ecc488 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/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)