mirror of
https://github.com/cline/cline.git
synced 2026-09-12 17:19:32 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58c67c5a1e | ||
|
|
bddc1b5e96 | ||
|
|
cb0de8f17e | ||
|
|
e1a0b244de | ||
|
|
a9d5411bf0 | ||
|
|
16af9125ec | ||
|
|
2792e7698f | ||
|
|
d02e5a89e5 | ||
|
|
20f19917d3 | ||
|
|
7e5cd52864 | ||
|
|
4622ad767b | ||
|
|
96048d5ac5 | ||
|
|
facec93082 | ||
|
|
c040be9eb1 | ||
|
|
8d3cf53289 | ||
|
|
83c4a82e6d | ||
|
|
6ad11badf3 | ||
|
|
39c7da301c | ||
|
|
5275f2eabc | ||
|
|
7cf68ff279 | ||
|
|
b8af02ebaa |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
disable autocapture
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Update deployer team name
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
added copy buttons to task header and assistant messages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add org to team affiliation check
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix-bedrock-api
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix for markdown copy excessive escape characters
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Only initialize posthog in the webview if the user has opted into telemetry
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding mistral medium-3 model
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
updated OR/cline provider to automate gemini models caching
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Raise Errors when users try to upload images larger than 7500x7500 pixels
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
timestamp visualization
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Allow the user to scroll when Cline is editing a file by disabling auto-scroll when the user scrolls up
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add image links to docs
|
||||
@@ -33,6 +33,7 @@ jobs:
|
||||
uses: morfien101/actions-authorized-user@4a3cfbf0bcb3cafe4a71710a278920c5d94bb38b
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
org: ${{ github.repository_owner }}
|
||||
team: "deployer"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [3.14.1]
|
||||
|
||||
- Disables autocaptures when initializing feature flags
|
||||
|
||||
## [3.14.0]
|
||||
|
||||
- Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile (Thanks @clicube!)
|
||||
|
||||
@@ -36,7 +36,7 @@ Cline offers the option of utilizing AWS credentials or AWS profiles to access A
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="/assets/robot_panel_dark.png"
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-aws-setup-markup%20(1).png"
|
||||
alt="AWS Bedrock configuration in Cline settings showing profile authentication setup"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
@@ -76,7 +76,10 @@ Vertex AI supports eight regions. Select a region that meets your latency, compl
|
||||
- Search for **Cline** and install the extension
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Cline extension in VS Code" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-extension-arrow.png"
|
||||
alt="Cline extension in VS Code"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 3.2 Configure Cline Settings
|
||||
|
||||
@@ -6,7 +6,7 @@ Cline supports major cloud providers like AWS Bedrock and Google's Cloud Vertex;
|
||||
|
||||
For the purpose of this document, we assume your organization will use cloud-based frontier models. Cloud inference providers offer cutting-edge capabilities and the flexibility to select models which best suit your needs.
|
||||
|
||||
Certain scenarios may warrant using local models, including handling highly sensitive data, applications requiring consistent low-latency responses, or compliance with strict data sovereignty requirements. If your team needs to utilize local models, see [Running Local Models ](/running-local-models/read-me-first.mdx)with Cline.
|
||||
Certain scenarios may warrant using local models, including handling highly sensitive data, applications requiring consistent low-latency responses, or compliance with strict data sovereignty requirements. If your team needs to utilize local models, see [Running Local Models ](/running-models-locally/read-me-first.mdx)with Cline.
|
||||
|
||||
---
|
||||
|
||||
@@ -14,9 +14,9 @@ Certain scenarios may warrant using local models, including handling highly sens
|
||||
|
||||
#### [IAM Security Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) (For administrators)
|
||||
|
||||
#### [AWS Bedrock setup for Legacy IAM (AWS Credentials)](/custom-model-configs/aws-bedrock.mdx)
|
||||
#### [AWS Bedrock setup for Legacy IAM (AWS Credentials)](/custom-model-configs/aws-bedrock-with-credentials-authentication.mdx)
|
||||
|
||||
#### [AWS Bedrock setup for SSO token (AWS Profile)](/custom-model-configs/aws-bedrock-w-profile-authentication.mdx)
|
||||
#### [AWS Bedrock setup for SSO token (AWS Profile)](/custom-model-configs/aws-bedrock-with-profile-authentication.mdx)
|
||||
|
||||
#### VPC Endpoint Setup
|
||||
|
||||
@@ -28,12 +28,12 @@ To protect your team's data, Cline supports VPC (Virtual Private Cloud) endpoint
|
||||
2. Follow the directions for [creating a VPC endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html#create-interface-endpoint-aws) in the AWS console. The image below pertains to steps 4 and 5 of the AWS guide linked above.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="VPC Console" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/vpc-console.png" alt="VPC Console" />
|
||||
</Frame>
|
||||
|
||||
3. Note the IP address of your VPC endpoint, open Cline's settings menu, and select `AWS Bedrock`from the API Provider dropdown.
|
||||
4. Click the `Use Custom VPC endpoint`checkbox and enter the IP address of your VPC endpoint
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="VPC Settings Menu" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/vpc-settings-menu.png" alt="VPC Settings Menu" />
|
||||
</Frame>
|
||||
|
||||
@@ -13,7 +13,10 @@ title: "Security Concerns"
|
||||
Cline operates exclusively as a client-side VSCode extension with zero server-side components. This fundamental design choice ensures that your code and data remain within your secure environment at all times. Unlike traditional AI assistants that send data to external servers for processing, Cline connects directly to your chosen cloud provider's AI endpoints, keeping all sensitive information within your infrastructure boundaries.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Cline's relationship to local and remote assets" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-arch.png"
|
||||
alt="Cline's relationship to local and remote assets"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Data Privacy Commitment
|
||||
|
||||
@@ -28,7 +28,10 @@ After each tool use, you can:
|
||||
2. Click the "Restore" button to open restore options
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Checkpoint comparison and restore options" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(13).png"
|
||||
alt="Checkpoint comparison and restore options"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### Rolling Back
|
||||
@@ -60,7 +63,7 @@ Checkpoints let you be more experimental with Cline. While human coding is often
|
||||
- Ideal for exploring different design patterns or architectural approaches
|
||||
|
||||
<Frame caption="In this case, I didn't like the changes Cline made to my robot dog-walking website (still working on the robots) and I wanted to revert both the codebase and the task to before any changes were made so I could start fresh.">
|
||||
<img src="/assets/robot_panel_dark.png" alt="Checkpoint restore demo" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/checkpointsDemo.gif" alt="Checkpoint restore demo" />
|
||||
</Frame>
|
||||
|
||||
### ✨ Best Practices
|
||||
@@ -104,5 +107,8 @@ Perhaps you didn't get the results you wanted, thought of a better way to phrase
|
||||
- Shift + Enter: Insert new line / line break
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Message editing interface" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/message-editing.png"
|
||||
alt="Message editing interface"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
+16
-7
@@ -7,7 +7,10 @@ title: "Plan & Act Modes: A Guide to Effective AI Development"
|
||||
Plan & Act modes represent Cline's approach to structured AI development, emphasizing thoughtful planning before implementation. This dual-mode system helps developers create more maintainable, accurate code while reducing iteration time.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Use Plan to gather context before using Act to implement the plan" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/planningThenActing%20(1).gif"
|
||||
alt="Use Plan to gather context before using Act to implement the plan"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Understanding the Modes
|
||||
@@ -27,7 +30,7 @@ Plan & Act modes represent Cline's approach to structured AI development, emphas
|
||||
- Can execute changes to your codebase
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Act mode capabilities" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(5).png" alt="Act mode capabilities" />
|
||||
</Frame>
|
||||
|
||||
### Workflow Guide
|
||||
@@ -39,7 +42,7 @@ Begin every significant development task in Plan mode:
|
||||
In this mode:
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Plan mode workflow" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(5)%20(1).png" alt="Plan mode workflow" />
|
||||
</Frame>
|
||||
|
||||
- Share your requirements
|
||||
@@ -48,7 +51,10 @@ In this mode:
|
||||
- Develop implementation strategy
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Planning phase" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2)%20(1)%20(1)%20(1).png"
|
||||
alt="Planning phase"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 2. Switch to Act Mode
|
||||
@@ -56,7 +62,7 @@ In this mode:
|
||||
Once you have a clear plan, switch to Act mode:
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Switching to Act mode" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/switching-to-act.gif" alt="Switching to Act mode" />
|
||||
</Frame>
|
||||
|
||||
Act mode allows Cline to:
|
||||
@@ -90,7 +96,10 @@ Complex projects often require multiple plan-act cycles:
|
||||
4. Document significant decisions
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Implementation best practices" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(3)%20(1).png"
|
||||
alt="Implementation best practices"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Power User Tips
|
||||
@@ -119,7 +128,7 @@ Complex projects often require multiple plan-act cycles:
|
||||
- Executing test cases
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Mode usage patterns" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(6).png" alt="Mode usage patterns" />
|
||||
</Frame>
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -22,7 +22,10 @@ Follow these steps to get Cline up and running:
|
||||
4. **Search for 'Cline':** In the Extensions search bar, type `Cline`.
|
||||
|
||||
<Frame caption="VS Code marketplace with Cline extension ready to install">
|
||||
<img src="/assets/robot_panel_dark.png" alt="VS Code marketplace showing Cline extension" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(20).png"
|
||||
alt="VS Code marketplace showing Cline extension"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
1. **Install the Extension:** Click the "Install" button next to the Cline extension.
|
||||
|
||||
@@ -28,7 +28,10 @@ Cline helps you manage this limitation with its Context Window Progress Bar, whi
|
||||
- The total capacity for your chosen model
|
||||
|
||||
<Frame caption="Visual representation of the context window usage in Cline">
|
||||
<img src="/assets/robot_panel_light.png" alt="Context window progress bar example" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(11).png"
|
||||
alt="Context window progress bar example"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
This visibility helps you work more effectively with Cline by letting you know when you might need to start fresh or break tasks into smaller chunks.
|
||||
|
||||
@@ -14,7 +14,7 @@ description: "Context is key to getting the most out of Cline"
|
||||
|
||||
<Frame caption="In a world of infinite context, the context window is what Cline currently has available">
|
||||
<img
|
||||
src="/assets/robot_panel_dark.png"
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2).png"
|
||||
alt="In a world of infinite context, the context window is what Cline currently has available"
|
||||
/>
|
||||
</Frame>
|
||||
@@ -65,7 +65,10 @@ Think of context like a whiteboard you and Cline share:
|
||||
Cline provides a visual way to monitor your context window usage through a progress bar:
|
||||
|
||||
<Frame caption="Visual representation of the context window usage">
|
||||
<img src="/assets/robot_panel_light.png" alt="Context window progress bar" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1)%20(1).png"
|
||||
alt="Context window progress bar"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Reading the Bar
|
||||
|
||||
@@ -11,7 +11,10 @@ Utilizing MCP servers will increase your token usage. Cline offers the ability t
|
||||
3. Cline will open a new settings window. find `Cline>Mcp:Mode` and make your selection from the dropdown menu.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="MCP settings edit" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/MCP-settings-edit%20(1).png"
|
||||
alt="MCP settings edit"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Managing Individual MCP Servers
|
||||
@@ -22,7 +25,10 @@ Each MCP server has its own configuration panel where you can modify settings, m
|
||||
2. Locate the MCP server you want to manage in the list, and open it by clicking on its name.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="MCP settings individual" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/MCP-settings-individual.png"
|
||||
alt="MCP settings individual"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Deleting a Server
|
||||
@@ -45,10 +51,6 @@ To set the maximum time to wait for a response after a tool call to the MCP serv
|
||||
|
||||
1. Click the `Network Timeout` dropdown at the bottom of the individual MCP server's config box and change the time. Default is 1 minute but it can be set between 30 seconds and 1 hour.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Network Timeout pulldown" />
|
||||
</Frame>
|
||||
|
||||
## Editing MCP Settings Files
|
||||
|
||||
Settings for all installed MCP servers are located in the `cline_mcp_settings.json` file:
|
||||
|
||||
@@ -14,7 +14,7 @@ Model Context Protocol is an open protocol that standardizes how applications pr
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="/assets/robot_panel_dark.png"
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/mcp-diagram.png"
|
||||
alt="MCP diagram showing how MCP servers connect LLMs to external tools and data sources"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
@@ -242,7 +242,10 @@ Let's walk through the development process of our AlphaAdvantage MCP server, whi
|
||||
### Planning Phase
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Planning phase demonstration" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/planning-phase.gif"
|
||||
alt="Planning phase demonstration"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
During the planning phase, we:
|
||||
@@ -267,7 +270,10 @@ During the planning phase, we:
|
||||
### Implementation
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Building MCP plugin demonstration" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/building-mcp-plugin.gif"
|
||||
alt="Building MCP plugin demonstration"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
We began by bootstrapping the project:
|
||||
|
||||
@@ -13,7 +13,7 @@ To get started with Cline Memory Bank:
|
||||
3. **Paste into Cline** - Add as custom instructions or in a .clinerules file
|
||||
4. **Initialize** - Ask Cline to "initialize memory bank"
|
||||
|
||||
[See detailed setup instructions](cline-memory-bank.md#getting-started-with-memory-bank)
|
||||
[See detailed setup instructions](#getting-started-with-memory-bank)
|
||||
|
||||
### Cline Memory Bank Custom Instructions \[COPY THIS]
|
||||
|
||||
@@ -152,7 +152,7 @@ The Memory Bank is a structured documentation system that allows Cline to mainta
|
||||
The Memory Bank isn't a Cline-specific feature - it's a methodology for managing AI context through structured documentation. When you instruct Cline to "follow custom instructions," it reads the Memory Bank files to rebuild its understanding of your project.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Workflow" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(15).png" alt="Memory Bank Workflow" />
|
||||
</Frame>
|
||||
|
||||
#### Understanding the Files
|
||||
@@ -162,7 +162,7 @@ Memory Bank files are simply markdown files you create in your project. They're
|
||||
Files are organized in a hierarchical structure that builds up a complete picture of your project:
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Memory Bank File Structure" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(16).png" alt="Memory Bank File Structure" />
|
||||
</Frame>
|
||||
|
||||
### Memory Bank Files Explained
|
||||
@@ -223,7 +223,7 @@ Create additional files when needed to organize:
|
||||
3. Ask Cline to "initialize memory bank"
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Setup" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(17).png" alt="Memory Bank Setup" />
|
||||
</Frame>
|
||||
|
||||
#### Project Brief Tips
|
||||
@@ -287,7 +287,7 @@ As you work with Cline, your context window will eventually fill up (note the pr
|
||||
This workflow ensures that important context is preserved in your Memory Bank files before the context window is cleared, allowing you to continue seamlessly in a fresh conversation.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Context Window" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(18).png" alt="Memory Bank Context Window" />
|
||||
</Frame>
|
||||
|
||||
#### How often should I update the memory bank?
|
||||
|
||||
@@ -22,7 +22,7 @@ To add custom instructions:
|
||||
4. Paste your instructions
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Cline Logo" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1).png" alt="Cline Logo" />
|
||||
</Frame>
|
||||
|
||||
Custom instructions are powerful for:
|
||||
@@ -207,7 +207,7 @@ Located conveniently under the chat input field, this popover allows you to:
|
||||
This UI significantly simplifies switching contexts and managing different sets of instructions without needing to manually edit files or configurations during a conversation.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Cline Logo" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1).png" alt="Cline Logo" />
|
||||
</Frame>
|
||||
|
||||
## .clineignore File Guide
|
||||
|
||||
@@ -20,7 +20,7 @@ Run AI models locally using LM Studio with Cline.
|
||||
- Download and install for your operating system
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="LM Studio download page" />
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(7).png" alt="LM Studio download page" />
|
||||
</Frame>
|
||||
|
||||
#### 2. Launch LM Studio
|
||||
@@ -29,7 +29,10 @@ Run AI models locally using LM Studio with Cline.
|
||||
- You'll see four tabs on the left: **Chat**, **Developer** (where you will start the server), **My Models** (where your downloaded models are stored), **Discover** (add new models)
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="LM Studio interface overview" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(10).png"
|
||||
alt="LM Studio interface overview"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 3. Download a Model
|
||||
@@ -39,7 +42,10 @@ Run AI models locally using LM Studio with Cline.
|
||||
- Wait for download to complete
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Downloading a model in LM Studio" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-download-model.gif"
|
||||
alt="Downloading a model in LM Studio"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 4. Start the Server
|
||||
@@ -49,7 +55,10 @@ Run AI models locally using LM Studio with Cline.
|
||||
- Note: The server will run at `http://localhost:1234`
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Starting the LM Studio server" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-starting-server.gif"
|
||||
alt="Starting the LM Studio server"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 5. Configure Cline
|
||||
@@ -60,7 +69,10 @@ Run AI models locally using LM Studio with Cline.
|
||||
4. Select your model from the available options
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Configuring Cline with LM Studio" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-select-model-cline.gif"
|
||||
alt="Configuring Cline with LM Studio"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### ⚠️ Important Notes
|
||||
|
||||
@@ -16,7 +16,10 @@ description: "A quick guide to setting up Ollama for local AI model execution wi
|
||||
- Download and install for your operating system
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Ollama download page" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2)%20(1)%20(1).png"
|
||||
alt="Ollama download page"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
#### 2. Choose and Download a Model
|
||||
@@ -29,7 +32,10 @@ description: "A quick guide to setting up Ollama for local AI model execution wi
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Selecting a model in Ollama" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/ollama-model-grab%20(2).gif"
|
||||
alt="Selecting a model in Ollama"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
- Open your Terminal and run the command:
|
||||
@@ -41,7 +47,10 @@ description: "A quick guide to setting up Ollama for local AI model execution wi
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Running Ollama in terminal" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/starting-ollama-terminal%20(2).gif"
|
||||
alt="Running Ollama in terminal"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
**✨ Your model is now ready to use within Cline!**
|
||||
@@ -56,7 +65,10 @@ description: "A quick guide to setting up Ollama for local AI model execution wi
|
||||
- Select the model from your available options
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Configuring Cline with Ollama" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/selecting-ollama-model-cline%20(3).gif"
|
||||
alt="Configuring Cline with Ollama"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### ⚠️ Important Notes
|
||||
|
||||
@@ -20,7 +20,10 @@ Local models are created by training a smaller model to imitate a larger one, bu
|
||||
Think of it like running your development environment on a calculator instead of a computer – it might handle basic tasks, but complex operations become unreliable or impossible.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/robot_panel_dark.png" alt="Local model comparison diagram" />
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(4).png"
|
||||
alt="Local model comparison diagram"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### What Actually Happens
|
||||
|
||||
Generated
+18
@@ -42,6 +42,7 @@
|
||||
"globby": "^14.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ignore": "^7.0.3",
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"mammoth": "^1.8.0",
|
||||
@@ -16579,6 +16580,18 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
|
||||
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"image-size": "bin/image-size.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -37429,6 +37442,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz",
|
||||
"integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA=="
|
||||
},
|
||||
"image-size": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
|
||||
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
|
||||
+6
-3
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.14.0",
|
||||
"version": "3.14.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -121,7 +121,7 @@
|
||||
},
|
||||
{
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"title": "✨ Generate Commit Message with Cline",
|
||||
"title": "Generate Commit Message with Cline",
|
||||
"category": "Cline",
|
||||
"icon": "$(robot)"
|
||||
}
|
||||
@@ -336,7 +336,9 @@
|
||||
"prepare": "husky",
|
||||
"changeset": "changeset",
|
||||
"version-packages": "changeset version",
|
||||
"docs:preview": "cd docs && mintlify dev",
|
||||
"docs": "cd docs && mintlify dev",
|
||||
"docs:check-links": "cd docs && mintlify broken-links",
|
||||
"docs:rename-file": "cd docs && mintlify rename",
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -409,6 +411,7 @@
|
||||
"globby": "^14.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ignore": "^7.0.3",
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"mammoth": "^1.8.0",
|
||||
|
||||
@@ -18,7 +18,8 @@ export class OpenAiHandler implements ApiHandler {
|
||||
// Use azureApiVersion to determine if this is an Azure endpoint, since the URL may not always contain 'azure.com'
|
||||
if (
|
||||
this.options.azureApiVersion ||
|
||||
(this.options.openAiBaseUrl?.toLowerCase().includes("azure.com") &&
|
||||
((this.options.openAiBaseUrl?.toLowerCase().includes("azure.com") ||
|
||||
this.options.openAiBaseUrl?.toLowerCase().includes("azure.us")) &&
|
||||
!this.options.openAiModelId?.toLowerCase().includes("deepseek"))
|
||||
) {
|
||||
this.client = new AzureOpenAI({
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function createOpenRouterStream(
|
||||
|
||||
// prompt caching: https://openrouter.ai/docs/prompt-caching
|
||||
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
// includes custom support for gemini which does not have iterative caching
|
||||
// handles direct model.id match logic
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3.7-sonnet":
|
||||
case "anthropic/claude-3.7-sonnet:beta":
|
||||
@@ -71,79 +71,78 @@ export async function createOpenRouterStream(
|
||||
}
|
||||
})
|
||||
break
|
||||
case "google/gemini-2.5-pro-preview-03-25":
|
||||
case "google/gemini-2.0-flash-001":
|
||||
case "google/gemini-flash-1.5":
|
||||
case "google/gemini-pro-1.5":
|
||||
// gemini only uses the last breakpoint for caching, so the others will be ignored
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: systemPrompt,
|
||||
// @ts-ignore-next-line
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// for safety, but this should always be the case
|
||||
if (openAiMessages.length >= 2) {
|
||||
const msg = openAiMessages[1]
|
||||
|
||||
if (msg) {
|
||||
if (typeof msg.content === "string") {
|
||||
msg.content = [{ type: "text", text: msg.content }]
|
||||
}
|
||||
if (Array.isArray(msg.content)) {
|
||||
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
|
||||
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
|
||||
|
||||
if (!lastTextPart) {
|
||||
lastTextPart = { type: "text", text: "..." }
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-ignore-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it doesn't make sense to alter breakpoints at all with the gemini cache implementation at this time
|
||||
/*const GEMINI_CACHE_USER_MESSAGE_INTERVAL = 4 // add new breakpoint every 4 turns
|
||||
const userMessages = openAiMessages.filter((msg) => msg.role === "user")
|
||||
|
||||
const userMessageCount = userMessages.length
|
||||
const targetUserMessageNumber =
|
||||
Math.floor(userMessageCount / GEMINI_CACHE_USER_MESSAGE_INTERVAL) * GEMINI_CACHE_USER_MESSAGE_INTERVAL
|
||||
|
||||
if (targetUserMessageNumber > 0) {
|
||||
// otherwise dont need to add a breakpoint
|
||||
const msg = userMessages[targetUserMessageNumber - 1]
|
||||
|
||||
if (msg) {
|
||||
if (typeof msg.content === "string") {
|
||||
msg.content = [{ type: "text", text: msg.content }]
|
||||
}
|
||||
if (Array.isArray(msg.content)) {
|
||||
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
|
||||
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
|
||||
|
||||
if (!lastTextPart) {
|
||||
lastTextPart = { type: "text", text: "..." }
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-ignore-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// handles gemini caching logic
|
||||
if (model.id.startsWith("google/") && model.info.supportsPromptCache) {
|
||||
// gemini only uses the last breakpoint for caching, so the others will be ignored
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: systemPrompt,
|
||||
// @ts-ignore-next-line
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// for safety, but this should always be the case
|
||||
if (openAiMessages.length >= 2) {
|
||||
const msg = openAiMessages[1]
|
||||
|
||||
if (msg) {
|
||||
if (typeof msg.content === "string") {
|
||||
msg.content = [{ type: "text", text: msg.content }]
|
||||
}
|
||||
if (Array.isArray(msg.content)) {
|
||||
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
|
||||
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
|
||||
|
||||
if (!lastTextPart) {
|
||||
lastTextPart = { type: "text", text: "..." }
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-ignore-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it doesn't make sense to alter breakpoints at all with the gemini cache implementation at this time
|
||||
/*const GEMINI_CACHE_USER_MESSAGE_INTERVAL = 4 // add new breakpoint every 4 turns
|
||||
const userMessages = openAiMessages.filter((msg) => msg.role === "user")
|
||||
|
||||
const userMessageCount = userMessages.length
|
||||
const targetUserMessageNumber =
|
||||
Math.floor(userMessageCount / GEMINI_CACHE_USER_MESSAGE_INTERVAL) * GEMINI_CACHE_USER_MESSAGE_INTERVAL
|
||||
|
||||
if (targetUserMessageNumber > 0) {
|
||||
// otherwise dont need to add a breakpoint
|
||||
const msg = userMessages[targetUserMessageNumber - 1]
|
||||
|
||||
if (msg) {
|
||||
if (typeof msg.content === "string") {
|
||||
msg.content = [{ type: "text", text: msg.content }]
|
||||
}
|
||||
if (Array.isArray(msg.content)) {
|
||||
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
|
||||
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
|
||||
|
||||
if (!lastTextPart) {
|
||||
lastTextPart = { type: "text", text: "..." }
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-ignore-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// Not sure how openrouter defaults max tokens when no value is provided, but the anthropic api requires this value and since they offer both 4096 and 8192 variants, we should ensure 8192.
|
||||
// (models usually default to max tokens allowed)
|
||||
let maxTokens: number | undefined
|
||||
|
||||
@@ -1301,14 +1301,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
modelInfo.cacheWritesPrice = 0.14
|
||||
modelInfo.cacheReadsPrice = 0.014
|
||||
break
|
||||
case "google/gemini-2.5-pro-preview-03-25":
|
||||
case "google/gemini-2.0-flash-001":
|
||||
case "google/gemini-flash-1.5":
|
||||
case "google/gemini-pro-1.5":
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
|
||||
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
|
||||
break
|
||||
default:
|
||||
if (rawModel.id.startsWith("openai/")) {
|
||||
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
|
||||
@@ -1317,6 +1309,12 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
|
||||
// openrouter charges no cache write pricing for openAI models
|
||||
}
|
||||
} else if (rawModel.id.startsWith("google/")) {
|
||||
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
|
||||
if (modelInfo.cacheReadsPrice) {
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1916,7 +1916,7 @@ export class Task {
|
||||
: "other_diff_error"
|
||||
|
||||
// Add telemetry for diff edit failure
|
||||
telemetryService.captureDiffEditFailure(this.taskId, errorType)
|
||||
telemetryService.captureDiffEditFailure(this.taskId, this.api.getModel().id, errorType)
|
||||
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
|
||||
@@ -26,6 +26,9 @@ export class DiffViewProvider {
|
||||
private streamedLines: string[] = []
|
||||
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
private fileEncoding: string = "utf8"
|
||||
private lastFirstVisibleLine: number = 0
|
||||
private shouldAutoScroll: boolean = true
|
||||
private scrollListener?: vscode.Disposable
|
||||
|
||||
constructor(private cwd: string) {}
|
||||
|
||||
@@ -34,6 +37,8 @@ export class DiffViewProvider {
|
||||
const fileExists = this.editType === "modify"
|
||||
const absolutePath = path.resolve(this.cwd, relPath)
|
||||
this.isEditing = true
|
||||
this.shouldAutoScroll = true
|
||||
this.lastFirstVisibleLine = 0
|
||||
// if the file is already open, ensure it's not dirty before getting its contents
|
||||
if (fileExists) {
|
||||
const existingDocument = vscode.workspace.textDocuments.find((doc) => arePathsEqual(doc.uri.fsPath, absolutePath))
|
||||
@@ -79,6 +84,21 @@ export class DiffViewProvider {
|
||||
this.fadedOverlayController.addLines(0, this.activeDiffEditor.document.lineCount)
|
||||
this.scrollEditorToLine(0) // will this crash for new files?
|
||||
this.streamedLines = []
|
||||
|
||||
// Add scroll detection to disable auto-scrolling when user scrolls up
|
||||
this.scrollListener = vscode.window.onDidChangeTextEditorVisibleRanges((e: vscode.TextEditorVisibleRangesChangeEvent) => {
|
||||
if (e.textEditor === this.activeDiffEditor) {
|
||||
const currentFirstVisibleLine = e.visibleRanges[0]?.start.line || 0
|
||||
|
||||
// If the first visible line moved upward, user scrolled up
|
||||
if (currentFirstVisibleLine < this.lastFirstVisibleLine) {
|
||||
this.shouldAutoScroll = false
|
||||
}
|
||||
|
||||
// Always update our tracking variable
|
||||
this.lastFirstVisibleLine = currentFirstVisibleLine
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async update(accumulatedContent: string, isFinal: boolean) {
|
||||
@@ -128,25 +148,30 @@ export class DiffViewProvider {
|
||||
this.activeLineController.setActiveLine(currentLine)
|
||||
this.fadedOverlayController.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
|
||||
// Scroll to the last changed line
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
// Scroll to the last changed line only if the user hasn't scrolled up
|
||||
if (this.shouldAutoScroll) {
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.InCenter)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(
|
||||
new vscode.Range(line, 0, line, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,5 +443,15 @@ export class DiffViewProvider {
|
||||
this.activeLineController = undefined
|
||||
this.streamedLines = []
|
||||
this.preDiagnostics = []
|
||||
|
||||
// Clean up the scroll listener
|
||||
if (this.scrollListener) {
|
||||
this.scrollListener.dispose()
|
||||
this.scrollListener = undefined
|
||||
}
|
||||
|
||||
// Reset auto-scroll state
|
||||
this.shouldAutoScroll = true
|
||||
this.lastFirstVisibleLine = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode"
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import sizeOf from "image-size"
|
||||
|
||||
export async function selectImages(): Promise<string[]> {
|
||||
const options: vscode.OpenDialogOptions = {
|
||||
@@ -17,16 +18,36 @@ export async function selectImages(): Promise<string[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
fileUris.map(async (uri) => {
|
||||
const imagePath = uri.fsPath
|
||||
const buffer = await fs.readFile(imagePath)
|
||||
const base64 = buffer.toString("base64")
|
||||
const mimeType = getMimeType(imagePath)
|
||||
const dataUrl = `data:${mimeType};base64,${base64}`
|
||||
return dataUrl
|
||||
}),
|
||||
)
|
||||
const processedImagePromises = fileUris.map(async (uri) => {
|
||||
const imagePath = uri.fsPath
|
||||
let buffer: Buffer
|
||||
try {
|
||||
// Read the file into a buffer first
|
||||
buffer = await fs.readFile(imagePath)
|
||||
// Convert Node.js Buffer to Uint8Array
|
||||
const uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
||||
const dimensions = sizeOf(uint8Array) // Get dimensions from Uint8Array
|
||||
if (dimensions.width! > 7500 || dimensions.height! > 7500) {
|
||||
console.warn(`Image dimensions exceed 7500px, skipping: ${imagePath}`)
|
||||
vscode.window.showErrorMessage(
|
||||
`Image too large: ${path.basename(imagePath)} was skipped (dimensions exceed 7500px).`,
|
||||
)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file or getting dimensions for ${imagePath}:`, error)
|
||||
vscode.window.showErrorMessage(`Could not read dimensions for ${path.basename(imagePath)}, skipping.`)
|
||||
return null
|
||||
}
|
||||
|
||||
// If dimensions are valid, proceed to convert the existing buffer to base64
|
||||
const base64 = buffer.toString("base64")
|
||||
const mimeType = getMimeType(imagePath)
|
||||
return `data:${mimeType};base64,${base64}`
|
||||
})
|
||||
|
||||
const dataUrlsWithNulls = await Promise.all(processedImagePromises)
|
||||
return dataUrlsWithNulls.filter((url) => url !== null) as string[] // Filter out skipped images
|
||||
}
|
||||
|
||||
function getMimeType(filePath: string): string {
|
||||
|
||||
@@ -512,13 +512,14 @@ class PostHogClient {
|
||||
* @param taskId Unique identifier for the task
|
||||
* @param errorType Type of error that occurred (e.g., "search_not_found", "invalid_format")
|
||||
*/
|
||||
public captureDiffEditFailure(taskId: string, errorType?: string, collect: boolean = false) {
|
||||
public captureDiffEditFailure(taskId: string, modelId: string, errorType?: string, collect: boolean = false) {
|
||||
this.capture(
|
||||
{
|
||||
event: PostHogClient.EVENTS.TASK.DIFF_EDIT_FAILED,
|
||||
properties: {
|
||||
taskId,
|
||||
errorType,
|
||||
modelId,
|
||||
},
|
||||
},
|
||||
collect,
|
||||
|
||||
+20
-6
@@ -204,27 +204,33 @@ export const bedrockModels = {
|
||||
contextWindow: 300_000,
|
||||
supportsImages: true,
|
||||
|
||||
supportsPromptCache: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.8,
|
||||
outputPrice: 3.2,
|
||||
// cacheWritesPrice: 3.2, // not written
|
||||
cacheReadsPrice: 0.2,
|
||||
},
|
||||
"amazon.nova-lite-v1:0": {
|
||||
maxTokens: 5000,
|
||||
contextWindow: 300_000,
|
||||
supportsImages: true,
|
||||
|
||||
supportsPromptCache: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.06,
|
||||
outputPrice: 0.24,
|
||||
// cacheWritesPrice: 0.24, // not written
|
||||
cacheReadsPrice: 0.015,
|
||||
},
|
||||
"amazon.nova-micro-v1:0": {
|
||||
maxTokens: 5000,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: false,
|
||||
|
||||
supportsPromptCache: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0.035,
|
||||
outputPrice: 0.14,
|
||||
// cacheWritesPrice: 0.14, // not written
|
||||
cacheReadsPrice: 0.00875,
|
||||
},
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0": {
|
||||
maxTokens: 8192,
|
||||
@@ -251,10 +257,10 @@ export const bedrockModels = {
|
||||
"anthropic.claude-3-5-haiku-20241022-v1:0": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: false,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 1.0,
|
||||
outputPrice: 5.0,
|
||||
inputPrice: 0.8,
|
||||
outputPrice: 4.0,
|
||||
cacheWritesPrice: 1.0,
|
||||
cacheReadsPrice: 0.08,
|
||||
},
|
||||
@@ -1387,6 +1393,14 @@ export const mistralModels = {
|
||||
inputPrice: 0.1,
|
||||
outputPrice: 0.3,
|
||||
},
|
||||
"mistral-medium-latest": {
|
||||
maxTokens: 128_000,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.4,
|
||||
outputPrice: 2.0,
|
||||
},
|
||||
"mistral-small-2501": {
|
||||
maxTokens: 32_000,
|
||||
contextWindow: 32_000,
|
||||
|
||||
@@ -34,20 +34,20 @@ describe("Path Utilities", () => {
|
||||
})
|
||||
|
||||
it("should show relative paths within cwd", () => {
|
||||
const cwd = "/home/user/project"
|
||||
const filePath = "/home/user/project/src/file.txt"
|
||||
const cwd = path.resolve("/home/user/project")
|
||||
const filePath = path.resolve("/home/user/project/src/file.txt")
|
||||
getReadablePath(cwd, filePath).should.equal("src/file.txt")
|
||||
})
|
||||
|
||||
it("should show basename when path equals cwd", () => {
|
||||
const cwd = "/home/user/project"
|
||||
const cwd = path.resolve("/home/user/project")
|
||||
getReadablePath(cwd, cwd).should.equal("project")
|
||||
})
|
||||
|
||||
it("should show absolute path when outside cwd", () => {
|
||||
const cwd = "/home/user/project"
|
||||
const filePath = "/home/user/other/file.txt"
|
||||
getReadablePath(cwd, filePath).should.equal(filePath)
|
||||
const cwd = path.resolve("/home/user/project")
|
||||
const filePath = path.resolve("/home/user/other/file.txt")
|
||||
getReadablePath(cwd, filePath).should.equal(filePath.toPosix())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,15 +74,6 @@ const AppContent = () => {
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (telemetrySetting === "enabled") {
|
||||
// posthog.identify(vscMachineId)
|
||||
// posthog.opt_in_capturing()
|
||||
// } else {
|
||||
// posthog.opt_out_capturing()
|
||||
// }
|
||||
// }, [telemetrySetting, vscMachineId])
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowAnnouncement) {
|
||||
setShowAnnouncement(true)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useEffect, type ReactNode } from "react"
|
||||
import { PostHogProvider } from "posthog-js/react"
|
||||
import posthog from "posthog-js"
|
||||
import { posthogConfig } from "@shared/services/config/posthog-config"
|
||||
import { useExtensionState } from "./context/ExtensionStateContext"
|
||||
|
||||
export function CustomPostHogProvider({ children }: { children: ReactNode }) {
|
||||
const { telemetrySetting } = useExtensionState()
|
||||
const isTelemetryEnabled = telemetrySetting === "enabled"
|
||||
|
||||
useEffect(() => {
|
||||
if (isTelemetryEnabled) {
|
||||
posthog.init(posthogConfig.apiKey, {
|
||||
api_host: posthogConfig.host,
|
||||
autocapture: false,
|
||||
disable_session_recording: true,
|
||||
})
|
||||
} else {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
}, [isTelemetryEnabled])
|
||||
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
import type { ReactNode } from "react"
|
||||
import { type ReactNode } from "react"
|
||||
|
||||
import { ExtensionStateContextProvider } from "./context/ExtensionStateContext"
|
||||
import { FirebaseAuthProvider } from "./context/FirebaseAuthContext"
|
||||
import { HeroUIProvider } from "@heroui/react"
|
||||
import { PostHogProvider } from "posthog-js/react"
|
||||
import { posthogConfig } from "@shared/services/config/posthog-config"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
posthog.init(posthogConfig.apiKey, {
|
||||
api_host: posthogConfig.host,
|
||||
})
|
||||
import { CustomPostHogProvider } from "./CustomPostHogProvider"
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ExtensionStateContextProvider>
|
||||
<PostHogProvider client={posthog}>
|
||||
<CustomPostHogProvider>
|
||||
<FirebaseAuthProvider>
|
||||
<HeroUIProvider>{children}</HeroUIProvider>
|
||||
</FirebaseAuthProvider>
|
||||
</PostHogProvider>
|
||||
</CustomPostHogProvider>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeBadge, VSCodeProgressRing, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, MouseEvent } from "react"
|
||||
|
||||
@@ -20,6 +20,62 @@ import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "@/utils
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { FileServiceClient } from "@/services/grpc-client"
|
||||
import { CheckmarkControl } from "@/components/common/CheckmarkControl"
|
||||
|
||||
interface CopyButtonProps {
|
||||
textToCopy: string | undefined
|
||||
}
|
||||
|
||||
const CopyButtonStyled = styled(VSCodeButton)`
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
`
|
||||
|
||||
interface WithCopyButtonProps {
|
||||
children: React.ReactNode
|
||||
textToCopy?: string
|
||||
style?: React.CSSProperties
|
||||
ref?: React.Ref<HTMLDivElement>
|
||||
onMouseUp?: (event: MouseEvent<HTMLDivElement>) => void
|
||||
}
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
&:hover ${CopyButtonStyled} {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const WithCopyButton = React.forwardRef<HTMLDivElement, WithCopyButtonProps>(
|
||||
({ children, textToCopy, style, onMouseUp, ...props }, ref) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!textToCopy) return
|
||||
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1500)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer ref={ref} onMouseUp={onMouseUp} style={style} {...props}>
|
||||
{children}
|
||||
{textToCopy && (
|
||||
<CopyButtonStyled appearance="icon" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy"}>
|
||||
<span className={`codicon codicon-${copied ? "check" : "copy"}`}></span>
|
||||
</CopyButtonStyled>
|
||||
)}
|
||||
</StyledContainer>
|
||||
)
|
||||
},
|
||||
)
|
||||
import { CheckpointControls, CheckpointOverlay } from "../common/CheckpointControls"
|
||||
import CodeAccordian, { cleanPathPrefix } from "../common/CodeAccordian"
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
|
||||
@@ -90,6 +146,7 @@ const Markdown = memo(({ markdown }: { markdown?: string }) => {
|
||||
overflowWrap: "anywhere",
|
||||
marginBottom: -15,
|
||||
marginTop: -15,
|
||||
overflow: "hidden", // contain child margins so that parent diff matches height of children
|
||||
}}>
|
||||
<MarkdownBlock markdown={markdown} />
|
||||
</div>
|
||||
@@ -901,7 +958,7 @@ export const ChatRowContent = ({
|
||||
return <McpResponseDisplay responseText={message.text || ""} />
|
||||
case "text":
|
||||
return (
|
||||
<div ref={contentRef} onMouseUp={handleMouseUp} style={{ position: "relative" }}>
|
||||
<WithCopyButton ref={contentRef} onMouseUp={handleMouseUp} textToCopy={message.text}>
|
||||
<Markdown markdown={message.text} />
|
||||
{quoteButtonState.visible && (
|
||||
<QuoteButton
|
||||
@@ -912,7 +969,7 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</WithCopyButton>
|
||||
)
|
||||
case "reasoning":
|
||||
return (
|
||||
@@ -1132,13 +1189,13 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={contentRef} // Added ref
|
||||
onMouseUp={handleMouseUp} // Added handler
|
||||
<WithCopyButton
|
||||
ref={contentRef}
|
||||
onMouseUp={handleMouseUp}
|
||||
textToCopy={text}
|
||||
style={{
|
||||
color: "var(--vscode-charts-green)",
|
||||
paddingTop: 10,
|
||||
position: "relative", // Added position
|
||||
}}>
|
||||
<Markdown markdown={text} />
|
||||
{quoteButtonState.visible && (
|
||||
@@ -1148,7 +1205,7 @@ export const ChatRowContent = ({
|
||||
onClick={handleQuoteClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</WithCopyButton>
|
||||
{message.partial !== true && hasChanges && (
|
||||
<div style={{ paddingTop: 17 }}>
|
||||
<SuccessButton
|
||||
@@ -1295,13 +1352,13 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={contentRef} // Added ref
|
||||
onMouseUp={handleMouseUp} // Added handler
|
||||
<WithCopyButton
|
||||
ref={contentRef}
|
||||
onMouseUp={handleMouseUp}
|
||||
textToCopy={text}
|
||||
style={{
|
||||
color: "var(--vscode-charts-green)",
|
||||
paddingTop: 10,
|
||||
position: "relative", // Added position
|
||||
}}>
|
||||
<Markdown markdown={text} />
|
||||
{quoteButtonState.visible && (
|
||||
@@ -1311,30 +1368,30 @@ export const ChatRowContent = ({
|
||||
onClick={handleQuoteClick}
|
||||
/>
|
||||
)}
|
||||
{message.partial !== true && hasChanges && (
|
||||
<div style={{ marginTop: 15 }}>
|
||||
<SuccessButton
|
||||
appearance="secondary"
|
||||
disabled={seeNewChangesDisabled}
|
||||
onClick={() => {
|
||||
setSeeNewChangesDisabled(true)
|
||||
vscode.postMessage({
|
||||
type: "taskCompletionViewChanges",
|
||||
number: message.ts,
|
||||
})
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-new-file"
|
||||
style={{
|
||||
marginRight: 6,
|
||||
cursor: seeNewChangesDisabled ? "wait" : "pointer",
|
||||
}}
|
||||
/>
|
||||
See new changes
|
||||
</SuccessButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</WithCopyButton>
|
||||
{message.partial !== true && hasChanges && (
|
||||
<div style={{ marginTop: 15 }}>
|
||||
<SuccessButton
|
||||
appearance="secondary"
|
||||
disabled={seeNewChangesDisabled}
|
||||
onClick={() => {
|
||||
setSeeNewChangesDisabled(true)
|
||||
vscode.postMessage({
|
||||
type: "taskCompletionViewChanges",
|
||||
number: message.ts,
|
||||
})
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-new-file"
|
||||
style={{
|
||||
marginRight: 6,
|
||||
cursor: seeNewChangesDisabled ? "wait" : "pointer",
|
||||
}}
|
||||
/>
|
||||
See new changes
|
||||
</SuccessButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@@ -1362,7 +1419,11 @@ export const ChatRowContent = ({
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div ref={contentRef} onMouseUp={handleMouseUp} style={{ position: "relative", paddingTop: 10 }}>
|
||||
<WithCopyButton
|
||||
ref={contentRef}
|
||||
onMouseUp={handleMouseUp}
|
||||
textToCopy={question}
|
||||
style={{ paddingTop: 10 }}>
|
||||
<Markdown markdown={question} />
|
||||
<OptionsButtons
|
||||
options={options}
|
||||
@@ -1379,7 +1440,7 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</WithCopyButton>
|
||||
</>
|
||||
)
|
||||
case "new_task":
|
||||
@@ -1428,7 +1489,7 @@ export const ChatRowContent = ({
|
||||
response = message.text
|
||||
}
|
||||
return (
|
||||
<div ref={contentRef} onMouseUp={handleMouseUp} style={{ position: "relative" }}>
|
||||
<WithCopyButton ref={contentRef} onMouseUp={handleMouseUp} textToCopy={response}>
|
||||
<Markdown markdown={response} />
|
||||
<OptionsButtons
|
||||
options={options}
|
||||
@@ -1445,7 +1506,7 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</WithCopyButton>
|
||||
)
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -40,6 +40,24 @@ import { ChatSettings } from "@shared/ChatSettings"
|
||||
import ServersToggleModal from "./ServersToggleModal"
|
||||
import ClineRulesToggleModal from "../cline-rules/ClineRulesToggleModal"
|
||||
|
||||
const getImageDimensions = (dataUrl: string): Promise<{ width: number; height: number }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
if (img.naturalWidth > 7500 || img.naturalHeight > 7500) {
|
||||
reject(new Error("Image dimensions exceed maximum allowed size of 7500px."))
|
||||
} else {
|
||||
resolve({ width: img.naturalWidth, height: img.naturalHeight })
|
||||
}
|
||||
}
|
||||
img.onerror = (err) => {
|
||||
console.error("Failed to load image for dimension check:", err)
|
||||
reject(new Error("Failed to load image to check dimensions."))
|
||||
}
|
||||
img.src = dataUrl
|
||||
})
|
||||
}
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
inputValue: string
|
||||
activeQuote: string | null
|
||||
@@ -276,6 +294,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
const shiftHoldTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const [showUnsupportedFileError, setShowUnsupportedFileError] = useState(false)
|
||||
const unsupportedFileTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const [showDimensionError, setShowDimensionError] = useState(false)
|
||||
const dimensionErrorTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([])
|
||||
const [searchLoading, setSearchLoading] = useState(false)
|
||||
@@ -733,6 +753,17 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
onFocusChange?.(false) // Call prop on blur
|
||||
}, [isMouseDownOnMenu, onFocusChange])
|
||||
|
||||
const showDimensionErrorMessage = useCallback(() => {
|
||||
setShowDimensionError(true)
|
||||
if (dimensionErrorTimerRef.current) {
|
||||
clearTimeout(dimensionErrorTimerRef.current)
|
||||
}
|
||||
dimensionErrorTimerRef.current = setTimeout(() => {
|
||||
setShowDimensionError(false)
|
||||
dimensionErrorTimerRef.current = null
|
||||
}, 3000)
|
||||
}, [])
|
||||
|
||||
const handlePaste = useCallback(
|
||||
async (e: React.ClipboardEvent) => {
|
||||
const items = e.clipboardData.items
|
||||
@@ -778,13 +809,24 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
return
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
reader.onloadend = async () => {
|
||||
if (reader.error) {
|
||||
console.error("Error reading file:", reader.error)
|
||||
resolve(null)
|
||||
} else {
|
||||
const result = reader.result
|
||||
resolve(typeof result === "string" ? result : null)
|
||||
if (typeof result === "string") {
|
||||
try {
|
||||
await getImageDimensions(result)
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
console.warn((error as Error).message)
|
||||
showDimensionErrorMessage()
|
||||
resolve(null)
|
||||
}
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
@@ -800,7 +842,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
}
|
||||
}
|
||||
},
|
||||
[shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue],
|
||||
[shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue, showDimensionErrorMessage],
|
||||
)
|
||||
|
||||
const handleThumbnailsHeightChange = useCallback((height: number) => {
|
||||
@@ -1230,13 +1272,25 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
(file) =>
|
||||
new Promise<string | null>((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
reader.onloadend = async () => {
|
||||
// Make async
|
||||
if (reader.error) {
|
||||
console.error("Error reading file:", reader.error)
|
||||
resolve(null)
|
||||
} else {
|
||||
const result = reader.result
|
||||
resolve(typeof result === "string" ? result : null)
|
||||
if (typeof result === "string") {
|
||||
try {
|
||||
await getImageDimensions(result) // Check dimensions
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
console.warn((error as Error).message)
|
||||
showDimensionErrorMessage() // Show error to user
|
||||
resolve(null) // Don't add this image
|
||||
}
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
@@ -1260,6 +1314,31 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}>
|
||||
{showDimensionError && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: "10px 15px",
|
||||
backgroundColor: "rgba(var(--vscode-errorForeground-rgb), 0.1)",
|
||||
border: "2px solid var(--vscode-errorForeground)",
|
||||
borderRadius: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 10, // Ensure it's above other elements
|
||||
pointerEvents: "none",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-errorForeground)",
|
||||
fontWeight: "bold",
|
||||
fontSize: "12px",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
Image dimensions exceed 7500px
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{showUnsupportedFileError && (
|
||||
<div
|
||||
style={{
|
||||
@@ -1380,7 +1459,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
}
|
||||
onHeightChange?.(height)
|
||||
}}
|
||||
placeholder={showUnsupportedFileError ? "" : placeholderText}
|
||||
placeholder={showUnsupportedFileError || showDimensionError ? "" : placeholderText}
|
||||
maxRows={10}
|
||||
autoFocus={true}
|
||||
style={{
|
||||
|
||||
@@ -41,6 +41,27 @@ interface ChatViewProps {
|
||||
showHistoryView: () => void
|
||||
}
|
||||
|
||||
// Function to clean up markdown escape characters
|
||||
function cleanupMarkdownEscapes(markdown: string): string {
|
||||
return (
|
||||
markdown
|
||||
// Handle underscores and asterisks (single or multiple)
|
||||
.replace(/\\([_*]+)/g, "$1")
|
||||
|
||||
// Handle angle brackets (for generics and XML)
|
||||
.replace(/\\([<>])/g, "$1")
|
||||
|
||||
// Handle backticks (for code)
|
||||
.replace(/\\(`)/g, "$1")
|
||||
|
||||
// Handle other common markdown special characters
|
||||
.replace(/\\([[\]()#.!])/g, "$1")
|
||||
|
||||
// Fix multiple consecutive backslashes
|
||||
.replace(/\\{2,}([_*`<>[\]()#.!])/g, "$1")
|
||||
)
|
||||
}
|
||||
|
||||
async function convertHtmlToMarkdown(html: string) {
|
||||
// Process the HTML to Markdown
|
||||
const result = await unified()
|
||||
@@ -55,10 +76,14 @@ async function convertHtmlToMarkdown(html: string) {
|
||||
rule: "-", // Use - for horizontal rules
|
||||
ruleSpaces: false, // No spaces in horizontal rules
|
||||
fences: true,
|
||||
escape: false,
|
||||
entities: false,
|
||||
})
|
||||
.process(html)
|
||||
|
||||
return String(result)
|
||||
const md = String(result)
|
||||
// Apply comprehensive cleanup of escape characters
|
||||
return cleanupMarkdownEscapes(md)
|
||||
}
|
||||
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||
@@ -120,7 +145,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
|
||||
// Convert HTML to Markdown
|
||||
const markdown = await convertHtmlToMarkdown(selectedHtml)
|
||||
|
||||
vscode.postMessage({ type: "copyToClipboard", text: markdown })
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
|
||||
import { validateSlashCommand } from "@/utils/slash-commands"
|
||||
import TaskTimeline from "./TaskTimeline"
|
||||
import { TaskServiceClient } from "@/services/grpc-client"
|
||||
import HeroTooltip from "@/components/common/HeroTooltip"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
task: ClineMessage
|
||||
@@ -136,12 +137,12 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
)
|
||||
}, [apiConfiguration?.apiProvider, apiConfiguration?.openAiModelInfo])
|
||||
|
||||
const shouldShowPromptCacheInfo =
|
||||
doesModelSupportPromptCache && apiConfiguration?.apiProvider !== "openrouter" && apiConfiguration?.apiProvider !== "cline"
|
||||
|
||||
const shouldShowPromptCacheInfoClineOR =
|
||||
doesModelSupportPromptCache &&
|
||||
(apiConfiguration?.apiProvider === "openrouter" || apiConfiguration?.apiProvider === "cline")
|
||||
const shouldShowPromptCacheInfo = () => {
|
||||
return (
|
||||
doesModelSupportPromptCache &&
|
||||
((cacheReads !== undefined && cacheReads > 0) || (cacheWrites !== undefined && cacheWrites > 0))
|
||||
)
|
||||
}
|
||||
|
||||
const ContextWindowComponent = (
|
||||
<>
|
||||
@@ -152,18 +153,6 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
flexDirection: windowWidth < 270 ? "column" : "row",
|
||||
gap: "4px",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
flexShrink: 0, // Prevents shrinking
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{/* {windowWidth > 280 && windowWidth < 310 ? "Context:" : "Context Window:"} */}
|
||||
Context Window:
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -172,7 +161,9 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
flex: 1,
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
<span>{formatLargeNumber(lastApiReqTotalTokens || 0)}</span>
|
||||
<HeroTooltip content="Current tokens used in this request">
|
||||
<span className="cursor-pointer">{formatLargeNumber(lastApiReqTotalTokens || 0)}</span>
|
||||
</HeroTooltip>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -180,24 +171,29 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
gap: "3px",
|
||||
flex: 1,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
height: "4px",
|
||||
backgroundColor: "color-mix(in srgb, var(--vscode-badge-foreground) 20%, transparent)",
|
||||
borderRadius: "2px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<HeroTooltip content="Context window usage">
|
||||
<div
|
||||
style={{
|
||||
width: `${((lastApiReqTotalTokens || 0) / contextWindow) * 100}%`,
|
||||
height: "100%",
|
||||
backgroundColor: "var(--vscode-badge-foreground)",
|
||||
flex: 1,
|
||||
height: "4px",
|
||||
backgroundColor: "color-mix(in srgb, var(--vscode-badge-foreground) 20%, transparent)",
|
||||
borderRadius: "2px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span>{formatLargeNumber(contextWindow)}</span>
|
||||
className="cursor-pointer">
|
||||
<div
|
||||
style={{
|
||||
width: `${((lastApiReqTotalTokens || 0) / contextWindow) * 100}%`,
|
||||
height: "100%",
|
||||
backgroundColor: "var(--vscode-badge-foreground)",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</HeroTooltip>
|
||||
<HeroTooltip content="Maximum context window size for this model">
|
||||
<span className="cursor-pointer">{formatLargeNumber(contextWindow)}</span>
|
||||
</HeroTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -263,7 +259,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
{!isTaskExpanded && <span style={{ marginLeft: 4 }}>{highlightText(task.text, false)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{!isTaskExpanded && isCostAvailable && (
|
||||
{isCostAvailable && (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
@@ -299,7 +295,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
ref={textRef}
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: isTextExpanded ? "unset" : 3,
|
||||
WebkitLineClamp: isTextExpanded ? "unset" : 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre-wrap",
|
||||
@@ -352,18 +348,19 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
</div>
|
||||
)}
|
||||
{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "4px",
|
||||
gap: "2px",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
height: 17,
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
@@ -375,77 +372,48 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: "bold" }}>Tokens:</span>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-up"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(tokensIn || 0)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-down"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(tokensOut || 0)}
|
||||
</span>
|
||||
<HeroTooltip content="Prompt Tokens">
|
||||
<span className="flex items-center gap-[3px] cursor-pointer">
|
||||
<i
|
||||
className="codicon codicon-arrow-up"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(tokensIn || 0)}
|
||||
</span>
|
||||
</HeroTooltip>
|
||||
<HeroTooltip content="Completion Tokens">
|
||||
<span className="flex items-center gap-[3px] cursor-pointer">
|
||||
<i
|
||||
className="codicon codicon-arrow-down"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(tokensOut || 0)}
|
||||
</span>
|
||||
</HeroTooltip>
|
||||
</div>
|
||||
{!isCostAvailable && (
|
||||
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
|
||||
{!shouldShowPromptCacheInfo() && (
|
||||
<div className="flex items-center flex-wrap">
|
||||
<CopyButton taskText={task.text} />
|
||||
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TaskTimeline messages={clineMessages} />
|
||||
|
||||
{shouldShowPromptCacheInfoClineOR && cacheReads !== undefined && (
|
||||
{shouldShowPromptCacheInfo() && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>Cache:</span>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-right"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: 0,
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(cacheReads || 0)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{shouldShowPromptCacheInfo &&
|
||||
(cacheReads !== undefined ||
|
||||
cacheWrites !== undefined ||
|
||||
apiConfiguration?.apiProvider === "anthropic") && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -453,62 +421,50 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
gap: "4px",
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>Cache:</span>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-database"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-1px",
|
||||
}}
|
||||
/>
|
||||
+{formatLargeNumber(cacheWrites || 0)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-right"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: 0,
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(cacheReads || 0)}
|
||||
</span>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: "bold" }}>Cache:</span>
|
||||
</div>
|
||||
{cacheWrites !== undefined && cacheWrites > 0 && (
|
||||
<HeroTooltip content="Tokens written to cache">
|
||||
<span className="flex items-center gap-[3px] cursor-pointer">
|
||||
<i
|
||||
className="codicon codicon-database"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-1px",
|
||||
}}
|
||||
/>
|
||||
+{formatLargeNumber(cacheWrites || 0)}
|
||||
</span>
|
||||
</HeroTooltip>
|
||||
)}
|
||||
{cacheReads !== undefined && cacheReads > 0 && (
|
||||
<HeroTooltip content="Tokens read from cache">
|
||||
<span className="flex items-center gap-[3px] cursor-pointer">
|
||||
<i
|
||||
className={"codicon codicon-arrow-right"}
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: 0,
|
||||
}}
|
||||
/>
|
||||
{formatLargeNumber(cacheReads || 0)}
|
||||
</span>
|
||||
</HeroTooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{ContextWindowComponent}
|
||||
{isCostAvailable && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
height: 17,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>API Cost:</span>
|
||||
<span>${totalCost?.toFixed(4)}</span>
|
||||
<div className="flex items-center flex-wrap">
|
||||
<CopyButton taskText={task.text} />
|
||||
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
|
||||
</div>
|
||||
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<TaskTimeline messages={clineMessages} />
|
||||
{ContextWindowComponent}
|
||||
</div>
|
||||
{checkpointTrackerErrorMessage && (
|
||||
<div
|
||||
style={{
|
||||
@@ -669,27 +625,59 @@ export const highlightText = (text?: string, withShadow = true) => {
|
||||
return [text]
|
||||
}
|
||||
|
||||
const CopyButton: React.FC<{
|
||||
taskText?: string
|
||||
}> = ({ taskText }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!taskText) return
|
||||
|
||||
navigator.clipboard.writeText(taskText).then(() => {
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1500)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<HeroTooltip content="Copy Task">
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={handleCopy}
|
||||
style={{ padding: "0px 0px" }}
|
||||
className="p-0"
|
||||
aria-label="Copy Task">
|
||||
<div className="flex items-center gap-[3px] text-[8px] font-bold opacity-60">
|
||||
<i className={`codicon codicon-${copied ? "check" : "copy"}`} />
|
||||
</div>
|
||||
</VSCodeButton>
|
||||
</HeroTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const DeleteButton: React.FC<{
|
||||
taskSize: string
|
||||
taskId?: string
|
||||
}> = ({ taskSize, taskId }) => (
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => taskId && TaskServiceClient.deleteTasksWithIds({ value: [taskId] })}
|
||||
style={{ padding: "0px 0px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
fontSize: "10px",
|
||||
fontWeight: "bold",
|
||||
opacity: 0.6,
|
||||
}}>
|
||||
<i className={`codicon codicon-trash`} />
|
||||
{taskSize}
|
||||
</div>
|
||||
</VSCodeButton>
|
||||
<HeroTooltip content="Delete Task & Checkpoints">
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => taskId && TaskServiceClient.deleteTasksWithIds({ value: [taskId] })}
|
||||
style={{ padding: "0px 0px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
fontSize: "10px",
|
||||
fontWeight: "bold",
|
||||
opacity: 0.6,
|
||||
}}>
|
||||
<i className={`codicon codicon-trash`} />
|
||||
{taskSize}
|
||||
</div>
|
||||
</VSCodeButton>
|
||||
</HeroTooltip>
|
||||
)
|
||||
|
||||
// const ExportButton = () => (
|
||||
|
||||
@@ -3,6 +3,13 @@ import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { combineApiRequests } from "@shared/combineApiRequests"
|
||||
import { combineCommandSequences } from "@shared/combineCommandSequences"
|
||||
import TaskTimelineTooltip from "./TaskTimelineTooltip"
|
||||
import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_GREEN } from "./colors"
|
||||
|
||||
// Timeline dimensions and spacing
|
||||
const TIMELINE_HEIGHT = "18px"
|
||||
const BLOCK_WIDTH = "9px"
|
||||
const BLOCK_GAP = "3px"
|
||||
const TOOLTIP_MARGIN = 32 // 32px margin on each side
|
||||
|
||||
interface TaskTimelineProps {
|
||||
messages: ClineMessage[]
|
||||
@@ -12,9 +19,11 @@ const getBlockColor = (message: ClineMessage): string => {
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "#FFFFFF" // White for system prompt
|
||||
return COLOR_WHITE // White for system prompt
|
||||
case "user_feedback":
|
||||
return COLOR_WHITE // White for user feedback
|
||||
case "text":
|
||||
return "#AAAAAA" // Gray for assistant responses
|
||||
return COLOR_GRAY // Gray for assistant responses
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
@@ -26,43 +35,62 @@ const getBlockColor = (message: ClineMessage): string => {
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return "#F5F5DC" // Beige for file read operations
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return "#3B82F6" // Blue for file edit/create operations
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return "#F5F5DC" // Default beige for tool use
|
||||
return COLOR_BEIGE // Default beige for tool use
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "#EF4444" // Red for terminal commands
|
||||
return COLOR_PURPLE // Red for terminal commands
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
return "#8B5CF6" // Purple for browser actions
|
||||
return COLOR_PURPLE // Purple for browser actions
|
||||
case "completion_result":
|
||||
return "#10B981" // Green for task success
|
||||
return COLOR_GREEN // Green for task success
|
||||
default:
|
||||
return "#9E9E9E" // Grey for unknown
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return "#AAAAAA" // Gray for user messages
|
||||
return COLOR_GRAY // Gray for user messages
|
||||
case "plan_mode_respond":
|
||||
return "#AAAAAA" // Gray for planning responses
|
||||
return COLOR_GRAY // Gray for planning responses
|
||||
case "tool":
|
||||
return "#9E9E9E" // Gray for tool approvals
|
||||
// Match the color of the tool approval with the tool type
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool approvals
|
||||
case "command":
|
||||
return "#9E9E9E" // Gray for command approvals
|
||||
return COLOR_PURPLE // Red for command approvals (same as terminal commands)
|
||||
case "browser_action_launch":
|
||||
return "#9E9E9E" // Gray for browser launch approvals
|
||||
return COLOR_PURPLE // Purple for browser launch approvals (same as browser actions)
|
||||
default:
|
||||
return "#9E9E9E" // Grey for unknown
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
}
|
||||
return "#9E9E9E" // Default grey
|
||||
return COLOR_WHITE // Default color
|
||||
}
|
||||
|
||||
const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
@@ -77,16 +105,27 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
const processed = combineApiRequests(combineCommandSequences(messages.slice(1)))
|
||||
|
||||
return processed.filter((msg) => {
|
||||
// Filter out standard "say" events we don't want to show
|
||||
if (
|
||||
msg.type === "say" &&
|
||||
(msg.say === "api_req_started" ||
|
||||
msg.say === "api_req_finished" ||
|
||||
msg.say === "api_req_retried" ||
|
||||
msg.say === "deleted_api_reqs" ||
|
||||
msg.say === "checkpoint_created" ||
|
||||
(msg.say === "text" && (!msg.text || msg.text.trim() === "")))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Filter out "ask" events we don't want to show, including the duplicate completion_result
|
||||
if (
|
||||
msg.type === "ask" &&
|
||||
(msg.ask === "resume_task" || msg.ask === "resume_completed_task" || msg.ask === "completion_result") // Filter out the duplicate completion_result "ask" message
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}, [messages])
|
||||
@@ -101,8 +140,6 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const TOOLTIP_MARGIN = 32 // 32px margin on each side
|
||||
|
||||
const handleMouseEnter = (message: ClineMessage, event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setHoveredMessage(message)
|
||||
|
||||
@@ -134,12 +171,13 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
ref={scrollableRef}
|
||||
style={{
|
||||
display: "flex",
|
||||
height: "10px",
|
||||
height: TIMELINE_HEIGHT,
|
||||
overflowX: "auto",
|
||||
scrollbarWidth: "none",
|
||||
msOverflowStyle: "none",
|
||||
width: "100%",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
gap: BLOCK_GAP, // Using flexbox gap instead of marginRight
|
||||
}}>
|
||||
<style>
|
||||
{`
|
||||
@@ -153,10 +191,9 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
width: "5px",
|
||||
width: BLOCK_WIDTH,
|
||||
height: "100%",
|
||||
backgroundColor: getBlockColor(message),
|
||||
marginRight: "1px",
|
||||
flexShrink: 0,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_GREEN } from "./colors"
|
||||
|
||||
// Color mapping for different message types
|
||||
|
||||
@@ -14,6 +15,8 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
// TODO: Need to confirm these classifcations with design
|
||||
case "task":
|
||||
return "Task Message"
|
||||
case "user_feedback":
|
||||
return "User Message"
|
||||
case "text":
|
||||
return "Assistant Response"
|
||||
case "tool":
|
||||
@@ -49,6 +52,8 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
return "Browser Result"
|
||||
case "completion_result":
|
||||
return "Task Completed"
|
||||
case "checkpoint_created":
|
||||
return "Checkpoint Created"
|
||||
default:
|
||||
return message.say || "Unknown"
|
||||
}
|
||||
@@ -59,11 +64,32 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
case "plan_mode_respond":
|
||||
return "Planning Response"
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return `File Read Approval: ${toolData.tool}`
|
||||
} else if (toolData.tool === "editedExistingFile") {
|
||||
return `File Edit Approval: ${toolData.path || "Unknown file"}`
|
||||
} else if (toolData.tool === "newFileCreated") {
|
||||
return `New File Approval: ${toolData.path || "Unknown file"}`
|
||||
}
|
||||
return `Tool Approval: ${toolData.tool}`
|
||||
} catch (e) {
|
||||
return "Tool Approval"
|
||||
}
|
||||
}
|
||||
return "Tool Approval"
|
||||
case "command":
|
||||
return "Command Approval"
|
||||
return "Terminal Command Approval"
|
||||
case "browser_action_launch":
|
||||
return "Browser Launch"
|
||||
return "Browser Action Approval"
|
||||
default:
|
||||
return message.ask || "Unknown"
|
||||
}
|
||||
@@ -99,8 +125,24 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
|
||||
const getTimestamp = (message: ClineMessage): string => {
|
||||
if (message.ts) {
|
||||
const date = new Date(message.ts)
|
||||
return date.toLocaleTimeString()
|
||||
const messageDate = new Date(message.ts)
|
||||
const today = new Date()
|
||||
|
||||
const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
const messageDateOnly = new Date(messageDate.getFullYear(), messageDate.getMonth(), messageDate.getDate())
|
||||
|
||||
const time = messageDate.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", hour12: true })
|
||||
|
||||
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
const monthName = monthNames[messageDate.getMonth()]
|
||||
|
||||
if (messageDateOnly.getTime() === todayDate.getTime()) {
|
||||
return `${time}`
|
||||
} else if (messageDate.getFullYear() === today.getFullYear()) {
|
||||
return `${monthName} ${messageDate.getDate()} ${time}`
|
||||
} else {
|
||||
return `${monthName} ${messageDate.getDate()}, ${messageDate.getFullYear()} ${time}`
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -110,9 +152,11 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
if (message.type === "say") {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "#FFFFFF" // White for system prompt
|
||||
return COLOR_WHITE // White for system prompt
|
||||
case "user_feedback":
|
||||
return COLOR_WHITE // White for user feedback
|
||||
case "text":
|
||||
return "#AAAAAA" // Gray for assistant responses
|
||||
return COLOR_GRAY // Gray for assistant responses
|
||||
case "tool":
|
||||
if (message.text) {
|
||||
try {
|
||||
@@ -124,43 +168,62 @@ const TaskTimelineTooltip: React.FC<TaskTimelineTooltipProps> = ({ message }) =>
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return "#F5F5DC" // Beige for file read operations
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return "#3B82F6" // Blue for file edit/create operations
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return "#F5F5DC" // Default beige for tool use
|
||||
return COLOR_BEIGE // Default beige for tool use
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "#EF4444" // Red for terminal commands
|
||||
return COLOR_PURPLE // Red for terminal commands
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
return "#8B5CF6" // Purple for browser actions
|
||||
return COLOR_PURPLE // Purple for browser actions
|
||||
case "completion_result":
|
||||
return "#10B981" // Green for task success
|
||||
return COLOR_GREEN // Green for task success
|
||||
default:
|
||||
return "#9E9E9E" // Grey for unknown
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
} else if (message.type === "ask") {
|
||||
switch (message.ask) {
|
||||
case "followup":
|
||||
return "#AAAAAA" // Gray for user messages
|
||||
return COLOR_GRAY // Gray for user messages
|
||||
case "plan_mode_respond":
|
||||
return "#AAAAAA" // Gray for planning responses
|
||||
return COLOR_GRAY // Gray for planning responses
|
||||
case "tool":
|
||||
return "#9E9E9E" // Gray for tool approvals
|
||||
// Match the color of the tool approval with the tool type
|
||||
if (message.text) {
|
||||
try {
|
||||
const toolData = JSON.parse(message.text)
|
||||
if (
|
||||
toolData.tool === "readFile" ||
|
||||
toolData.tool === "listFilesTopLevel" ||
|
||||
toolData.tool === "listFilesRecursive" ||
|
||||
toolData.tool === "listCodeDefinitionNames" ||
|
||||
toolData.tool === "searchFiles"
|
||||
) {
|
||||
return COLOR_BEIGE // Beige for file read operations
|
||||
} else if (toolData.tool === "editedExistingFile" || toolData.tool === "newFileCreated") {
|
||||
return COLOR_BLUE // Blue for file edit/create operations
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON parse error here
|
||||
}
|
||||
}
|
||||
return COLOR_BEIGE // Default beige for tool approvals
|
||||
case "command":
|
||||
return "#9E9E9E" // Gray for command approvals
|
||||
return COLOR_PURPLE // Red for command approvals (same as terminal commands)
|
||||
case "browser_action_launch":
|
||||
return "#9E9E9E" // Gray for browser launch approvals
|
||||
return COLOR_PURPLE // Purple for browser launch approvals (same as browser actions)
|
||||
default:
|
||||
return "#9E9E9E" // Grey for unknown
|
||||
return COLOR_DARK_GRAY // Dark gray for unknown
|
||||
}
|
||||
}
|
||||
return "#9E9E9E" // Default grey
|
||||
return COLOR_DARK_GRAY // Default dark gray
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Color constants for timeline and tooltips
|
||||
export const COLOR_WHITE = "#FFFFFF" // White for system prompt and user feedback
|
||||
export const COLOR_GRAY = "#AAAAAA" // Gray for assistant responses and user messages
|
||||
export const COLOR_DARK_GRAY = "#9E9E9E" // Dark gray for unknown types
|
||||
export const COLOR_BEIGE = "#FFFF54" // Beige for file read operations
|
||||
export const COLOR_BLUE = "#3B82F6" // Blue for file edit/create operations
|
||||
export const COLOR_RED = "#EF4444" // Red for terminal commands
|
||||
export const COLOR_PURPLE = "#9E59FA" // Purple for browser actions
|
||||
export const COLOR_GREEN = "#10B981" // Green for task success
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from "react"
|
||||
import { Tooltip } from "@heroui/react"
|
||||
|
||||
interface HeroTooltipProps {
|
||||
content: React.ReactNode
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
delay?: number
|
||||
closeDelay?: number
|
||||
placement?: "top" | "bottom" | "left" | "right"
|
||||
}
|
||||
|
||||
/**
|
||||
* HeroTooltip component that wraps the HeroUI tooltip with styling
|
||||
* similar to TaskTimelineTooltip
|
||||
*/
|
||||
const HeroTooltip: React.FC<HeroTooltipProps> = ({
|
||||
content,
|
||||
children,
|
||||
className,
|
||||
delay = 0,
|
||||
closeDelay = 500,
|
||||
placement = "top",
|
||||
}) => {
|
||||
// If content is a simple string, wrap it in the tailwind styled divs
|
||||
const formattedContent =
|
||||
typeof content === "string" ? (
|
||||
<div
|
||||
className={`bg-[var(--vscode-editor-background)] text-[var(--vscode-editor-foreground)]
|
||||
border border-[var(--vscode-widget-border)] rounded p-2 w-full shadow-md text-xs max-w-[250px] ${className}`}>
|
||||
<div
|
||||
className="whitespace-pre-wrap break-words max-h-[150px] overflow-y-auto text-[11px]
|
||||
font-[var(--vscode-editor-font-family)] p-1 rounded">
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// If content is already a React node, assume it's pre-formatted
|
||||
content
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={formattedContent}
|
||||
delay={delay}
|
||||
closeDelay={0} // Immediate close when cursor moves away
|
||||
placement={placement}
|
||||
isDisabled={false}
|
||||
showArrow={false}
|
||||
disableAnimation={true} // Disable animation for immediate appearance/disappearance
|
||||
classNames={{
|
||||
content: "hero-tooltip-content pointer-events-none", // Prevent hovering over tooltip
|
||||
}}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default HeroTooltip
|
||||
@@ -10,7 +10,6 @@ import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import BrowserSettingsSection from "./BrowserSettingsSection"
|
||||
import TerminalSettingsSection from "./TerminalSettingsSection"
|
||||
import { useFeatureFlag } from "@/hooks/useFeatureFlag"
|
||||
import { FEATURE_FLAGS } from "@shared/services/feature-flags/feature-flags"
|
||||
const { IS_DEV } = process.env
|
||||
|
||||
@@ -146,8 +145,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
handleSubmit(true)
|
||||
}
|
||||
|
||||
const showCustomInstructions = useFeatureFlag(FEATURE_FLAGS.CUSTOM_INSTRUCTIONS)
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 right-0 bottom-0 pt-[10px] pr-0 pb-0 pl-5 flex flex-col overflow-hidden">
|
||||
<div className="flex justify-between items-center mb-[13px] pr-[17px]">
|
||||
@@ -186,24 +183,20 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCustomInstructions && (
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
resize="vertical"
|
||||
rows={4}
|
||||
placeholder={
|
||||
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
|
||||
}
|
||||
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
|
||||
<span className="font-medium">Custom Instructions</span>
|
||||
</VSCodeTextArea>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
These instructions are added to the end of the system prompt sent with every request.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
resize="vertical"
|
||||
rows={4}
|
||||
placeholder={'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'}
|
||||
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
|
||||
<span className="font-medium">Custom Instructions</span>
|
||||
</VSCodeTextArea>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
These instructions are added to the end of the system prompt sent with every request.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeCheckbox
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useFeatureFlagPayload } from "posthog-js/react"
|
||||
|
||||
export const useFeatureFlag = (flagName: string): boolean => {
|
||||
const payload = useFeatureFlagPayload(flagName) as { enabled: boolean }
|
||||
if (payload && payload.enabled) {
|
||||
return payload.enabled
|
||||
const { telemetrySetting } = useExtensionState()
|
||||
|
||||
try {
|
||||
const payload = useFeatureFlagPayload(flagName) as { enabled: boolean }
|
||||
if (payload && typeof payload === "object") {
|
||||
// Check if the enabled property exists and is a boolean
|
||||
if ("enabled" in payload && typeof payload.enabled === "boolean") {
|
||||
return payload.enabled
|
||||
}
|
||||
}
|
||||
|
||||
if (telemetrySetting === "enabled") {
|
||||
console.warn(`Feature flag ${flagName} not found or missing enabled property.`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error retrieving feature flag "${flagName}":`, error)
|
||||
}
|
||||
console.warn(`Feature flag ${flagName} not found or missing enabled property.`)
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user