mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46ef339198 | |||
| 964660892a | |||
| 2e33b042c8 | |||
| bca2fb4bf9 | |||
| c67cde2038 | |||
| 17fc8cf288 |
+1
-1
@@ -112,7 +112,7 @@ message UpdateSettingsRequest {
|
||||
optional int64 shell_integration_timeout = 8;
|
||||
optional bool terminal_reuse_enabled = 9;
|
||||
optional bool mcp_responses_collapsed = 10;
|
||||
optional bool mcp_rich_display_enabled = 11;
|
||||
optional string mcp_display_mode = 11;
|
||||
optional int64 terminal_output_line_limit = 12;
|
||||
}
|
||||
|
||||
|
||||
@@ -834,7 +834,7 @@ export class Controller {
|
||||
chatSettings: storedChatSettings,
|
||||
userInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpRichDisplayEnabled,
|
||||
mcpDisplayMode,
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting,
|
||||
@@ -887,7 +887,7 @@ export class Controller {
|
||||
chatSettings,
|
||||
userInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpRichDisplayEnabled,
|
||||
mcpDisplayMode,
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting: enableCheckpointsSetting ?? true,
|
||||
|
||||
@@ -50,9 +50,9 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
await controller.context.globalState.update("mcpResponsesCollapsed", request.mcpResponsesCollapsed)
|
||||
}
|
||||
|
||||
// Update MCP responses collapsed setting
|
||||
if (request.mcpRichDisplayEnabled !== undefined) {
|
||||
await controller.context.globalState.update("mcpRichDisplayEnabled", request.mcpRichDisplayEnabled)
|
||||
// Update MCP display mode setting
|
||||
if (request.mcpDisplayMode !== undefined) {
|
||||
await controller.context.globalState.update("mcpDisplayMode", request.mcpDisplayMode)
|
||||
}
|
||||
|
||||
// Update chat settings
|
||||
|
||||
@@ -76,7 +76,7 @@ export type GlobalStateKey =
|
||||
| "isNewUser"
|
||||
| "welcomeViewCompleted"
|
||||
| "terminalOutputLineLimit"
|
||||
| "mcpRichDisplayEnabled"
|
||||
| "mcpDisplayMode"
|
||||
| "sapAiCoreTokenUrl"
|
||||
| "sapAiCoreBaseUrl"
|
||||
| "sapAiResourceGroup"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { DEFAULT_MCP_DISPLAY_MODE, McpDisplayMode } from "@shared/McpDisplayMode"
|
||||
import { migrateEnableCheckpointsSetting, migrateMcpMarketplaceEnableSetting } from "./state-migrations"
|
||||
/*
|
||||
Storage
|
||||
@@ -173,7 +174,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
shellIntegrationTimeout,
|
||||
enableCheckpointsSettingRaw,
|
||||
mcpMarketplaceEnabledRaw,
|
||||
mcpRichDisplayEnabled,
|
||||
mcpDisplayMode,
|
||||
mcpResponsesCollapsedRaw,
|
||||
globalWorkflowToggles,
|
||||
terminalReuseEnabled,
|
||||
@@ -248,7 +249,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
|
||||
getGlobalState(context, "enableCheckpointsSetting") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "mcpMarketplaceEnabled") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "mcpRichDisplayEnabled") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "mcpDisplayMode") as Promise<McpDisplayMode | undefined>,
|
||||
getGlobalState(context, "mcpResponsesCollapsed") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "globalWorkflowToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
getGlobalState(context, "terminalReuseEnabled") as Promise<boolean | undefined>,
|
||||
@@ -470,7 +471,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
previousModeSapAiCoreModelId,
|
||||
mcpMarketplaceEnabled: mcpMarketplaceEnabled,
|
||||
mcpRichDisplayEnabled: mcpRichDisplayEnabled ?? true,
|
||||
mcpDisplayMode: mcpDisplayMode ?? DEFAULT_MCP_DISPLAY_MODE,
|
||||
mcpResponsesCollapsed: mcpResponsesCollapsed,
|
||||
telemetrySetting: telemetrySetting || "unset",
|
||||
planActSeparateModelsSetting,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { HistoryItem } from "./HistoryItem"
|
||||
import { TelemetrySetting } from "./TelemetrySetting"
|
||||
import { ClineRulesToggles } from "./cline-rules"
|
||||
import { UserInfo } from "./UserInfo"
|
||||
import { McpDisplayMode, DEFAULT_MCP_DISPLAY_MODE } from "./McpDisplayMode"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
@@ -37,7 +38,7 @@ export interface ExtensionState {
|
||||
clineMessages: ClineMessage[]
|
||||
currentTaskItem?: HistoryItem
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
mcpRichDisplayEnabled: boolean
|
||||
mcpDisplayMode: McpDisplayMode
|
||||
planActSeparateModelsSetting: boolean
|
||||
enableCheckpointsSetting?: boolean
|
||||
platform: Platform
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Represents the different display modes available for MCP responses
|
||||
*/
|
||||
export type McpDisplayMode = "rich" | "plain" | "markdown"
|
||||
|
||||
/**
|
||||
* Default display mode for MCP responses
|
||||
*/
|
||||
export const DEFAULT_MCP_DISPLAY_MODE: McpDisplayMode = "plain"
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import { McpDisplayMode } from "@shared/McpDisplayMode"
|
||||
|
||||
interface McpDisplayModeDropdownProps {
|
||||
value: McpDisplayMode
|
||||
onChange: (mode: McpDisplayMode) => void
|
||||
id?: string
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
const McpDisplayModeDropdown: React.FC<McpDisplayModeDropdownProps> = ({ value, onChange, id, className, style, onClick }) => {
|
||||
const handleChange = (e: any) => {
|
||||
const newMode = e.target.value as McpDisplayMode
|
||||
onChange(newMode)
|
||||
}
|
||||
|
||||
return (
|
||||
<VSCodeDropdown id={id} value={value} onChange={handleChange} onClick={onClick} className={className} style={style}>
|
||||
<VSCodeOption value="plain">Plain Text</VSCodeOption>
|
||||
<VSCodeOption value="rich">Rich Display</VSCodeOption>
|
||||
<VSCodeOption value="markdown">Markdown</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpDisplayModeDropdown
|
||||
@@ -1,11 +1,16 @@
|
||||
import React, { useEffect, useState, useCallback } from "react"
|
||||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" // Import ProgressRing
|
||||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import LinkPreview from "./LinkPreview"
|
||||
import ImagePreview from "./ImagePreview"
|
||||
import styled from "styled-components"
|
||||
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
import ChatErrorBoundary from "@/components/chat/ChatErrorBoundary"
|
||||
import MarkdownBlock from "@/components/common/MarkdownBlock"
|
||||
import McpDisplayModeDropdown from "./McpDisplayModeDropdown"
|
||||
import { DropdownContainer } from "@/components/settings/ApiOptions"
|
||||
import { updateSetting } from "@/components/settings/utils/settingsHandlers"
|
||||
import { McpDisplayMode } from "@shared/McpDisplayMode"
|
||||
import { UrlMatch, processResponseUrls, DisplaySegment, buildDisplaySegments } from "./utils/mcpRichUtil"
|
||||
|
||||
// Maximum number of URLs to process in total, per response
|
||||
@@ -36,46 +41,6 @@ const ResponseHeader = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleSwitch = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
.toggle-label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.toggle-container {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.toggle-container.active {
|
||||
background-color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
.toggle-handle {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--vscode-button-foreground);
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.toggle-container.active .toggle-handle {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
`
|
||||
|
||||
const ResponseContainer = styled.div`
|
||||
position: relative;
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
@@ -108,16 +73,15 @@ interface McpResponseDisplayProps {
|
||||
}
|
||||
|
||||
const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText }) => {
|
||||
const { mcpResponsesCollapsed, mcpRichDisplayEnabled } = useExtensionState() // Get setting from context
|
||||
const { mcpResponsesCollapsed, mcpDisplayMode } = useExtensionState() // Get setting from context
|
||||
const [isExpanded, setIsExpanded] = useState(!mcpResponsesCollapsed) // Initialize with context setting
|
||||
const [isLoading, setIsLoading] = useState(false) // Initial loading state for rich content
|
||||
const [displayMode, setDisplayMode] = useState<"rich" | "plain">(mcpRichDisplayEnabled ? "rich" : "plain")
|
||||
|
||||
const [urlMatches, setUrlMatches] = useState<UrlMatch[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const toggleDisplayMode = useCallback(() => {
|
||||
setDisplayMode((prevMode) => (prevMode === "rich" ? "plain" : "rich"))
|
||||
const handleDisplayModeChange = useCallback((newMode: McpDisplayMode) => {
|
||||
updateSetting("mcpDisplayMode", newMode)
|
||||
}, [])
|
||||
|
||||
const toggleExpand = useCallback(() => {
|
||||
@@ -131,11 +95,11 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
// Find all URLs in the text and determine if they're images
|
||||
useEffect(() => {
|
||||
// Skip all processing if in plain mode
|
||||
if (!isExpanded || displayMode === "plain") {
|
||||
// Skip all processing if in plain mode or markdown mode
|
||||
if (!isExpanded || mcpDisplayMode === "plain" || mcpDisplayMode === "markdown") {
|
||||
setIsLoading(false)
|
||||
if (urlMatches.length > 0) {
|
||||
setUrlMatches([]) // Clear any existing matches when in plain mode
|
||||
setUrlMatches([]) // Clear any existing matches when not in rich mode
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -162,10 +126,10 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
)
|
||||
|
||||
return cleanup
|
||||
}, [responseText, displayMode, isExpanded])
|
||||
}, [responseText, mcpDisplayMode, isExpanded])
|
||||
|
||||
// Helper function to render a display segment
|
||||
const renderSegment = useCallback((segment: DisplaySegment): JSX.Element => {
|
||||
const renderSegment = (segment: DisplaySegment): JSX.Element => {
|
||||
switch (segment.type) {
|
||||
case "text":
|
||||
case "url":
|
||||
@@ -205,7 +169,7 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
default:
|
||||
return <React.Fragment key={segment.key} />
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
// Function to render content based on display mode
|
||||
const renderContent = () => {
|
||||
@@ -213,7 +177,7 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
return null
|
||||
}
|
||||
|
||||
if (isLoading && displayMode === "rich") {
|
||||
if (isLoading && mcpDisplayMode === "rich") {
|
||||
return (
|
||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px" }}>
|
||||
<VSCodeProgressRing />
|
||||
@@ -221,10 +185,14 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
)
|
||||
}
|
||||
|
||||
if (displayMode === "plain") {
|
||||
if (mcpDisplayMode === "plain") {
|
||||
return <UrlText>{responseText}</UrlText>
|
||||
}
|
||||
|
||||
if (mcpDisplayMode === "markdown") {
|
||||
return <MarkdownBlock markdown={responseText} />
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
@@ -234,7 +202,7 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
)
|
||||
}
|
||||
|
||||
if (displayMode === "rich") {
|
||||
if (mcpDisplayMode === "rich") {
|
||||
const segments = buildDisplaySegments(responseText, urlMatches)
|
||||
return <>{segments.map(renderSegment)}</>
|
||||
}
|
||||
@@ -255,16 +223,15 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} header-icon`}></span>
|
||||
Response
|
||||
</div>
|
||||
<div style={{ minWidth: isExpanded ? "auto" : "0", visibility: isExpanded ? "visible" : "hidden" }}>
|
||||
<ToggleSwitch onClick={(e) => e.stopPropagation()}>
|
||||
<span className="toggle-label">{displayMode === "rich" ? "Rich Display" : "Plain Text"}</span>
|
||||
<div
|
||||
className={`toggle-container ${displayMode === "rich" ? "active" : ""}`}
|
||||
onClick={toggleDisplayMode}>
|
||||
<div className="toggle-handle"></div>
|
||||
</div>
|
||||
</ToggleSwitch>
|
||||
</div>
|
||||
<DropdownContainer
|
||||
style={{ minWidth: isExpanded ? "auto" : "0", visibility: isExpanded ? "visible" : "hidden" }}>
|
||||
<McpDisplayModeDropdown
|
||||
value={mcpDisplayMode}
|
||||
onChange={handleDisplayModeChange}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ minWidth: "120px" }}
|
||||
/>
|
||||
</DropdownContainer>
|
||||
</ResponseHeader>
|
||||
|
||||
{isExpanded && <div className="response-content">{renderContent()}</div>}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { memo } from "react"
|
||||
import { OpenAIReasoningEffort } from "@shared/ChatSettings"
|
||||
import { updateSetting } from "../utils/settingsHandlers"
|
||||
import { convertChatSettingsToProtoChatSettings } from "@shared/proto-conversions/state/chat-settings-conversion"
|
||||
import { McpDisplayMode } from "@shared/McpDisplayMode"
|
||||
import McpDisplayModeDropdown from "@/components/mcp/chat-display/McpDisplayModeDropdown"
|
||||
import Section from "../Section"
|
||||
|
||||
interface FeatureSettingsSectionProps {
|
||||
@@ -11,7 +13,7 @@ interface FeatureSettingsSectionProps {
|
||||
}
|
||||
|
||||
const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionProps) => {
|
||||
const { enableCheckpointsSetting, mcpMarketplaceEnabled, mcpRichDisplayEnabled, mcpResponsesCollapsed, chatSettings } =
|
||||
const { enableCheckpointsSetting, mcpMarketplaceEnabled, mcpDisplayMode, mcpResponsesCollapsed, chatSettings } =
|
||||
useExtensionState()
|
||||
|
||||
const handleReasoningEffortChange = (newValue: OpenAIReasoningEffort) => {
|
||||
@@ -59,16 +61,20 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<VSCodeCheckbox
|
||||
checked={mcpRichDisplayEnabled}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
updateSetting("mcpRichDisplayEnabled", checked)
|
||||
}}>
|
||||
Enable Rich MCP Display
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs text-[var(--vscode-descriptionForeground)]">
|
||||
Enables rich formatting for MCP responses. When disabled, responses will be shown in plain text.
|
||||
<label
|
||||
htmlFor="mcp-display-mode-dropdown"
|
||||
className="block text-sm font-medium text-[var(--vscode-foreground)] mb-1">
|
||||
MCP Display Mode
|
||||
</label>
|
||||
<McpDisplayModeDropdown
|
||||
id="mcp-display-mode-dropdown"
|
||||
value={mcpDisplayMode}
|
||||
onChange={(newMode: McpDisplayMode) => updateSetting("mcpDisplayMode", newMode)}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
Controls how MCP responses are displayed: plain text, rich formatting with links/images, or markdown
|
||||
rendering.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
|
||||
@@ -31,6 +31,7 @@ import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/share
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { OpenRouterCompatibleModelInfo } from "@shared/proto/models"
|
||||
import { UserInfo } from "@shared/proto/account"
|
||||
import { DEFAULT_MCP_DISPLAY_MODE } from "@shared/McpDisplayMode"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
@@ -189,7 +190,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
distinctId: "",
|
||||
planActSeparateModelsSetting: true,
|
||||
enableCheckpointsSetting: true,
|
||||
mcpRichDisplayEnabled: true,
|
||||
mcpDisplayMode: DEFAULT_MCP_DISPLAY_MODE,
|
||||
globalClineRulesToggles: {},
|
||||
localClineRulesToggles: {},
|
||||
localCursorRulesToggles: {},
|
||||
@@ -762,7 +763,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
planActSeparateModelsSetting: state.planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting: state.enableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled: state.mcpMarketplaceEnabled,
|
||||
mcpRichDisplayEnabled: state.mcpRichDisplayEnabled,
|
||||
mcpDisplayMode: state.mcpDisplayMode,
|
||||
mcpResponsesCollapsed: state.mcpResponsesCollapsed,
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user