Compare commits

...
Author SHA1 Message Date
Elephant Lumps b0b2c84606 theme-adapting-opacity-transition-version 2025-05-17 12:32:32 -07:00
Elephant Lumps 345b6c03a0 purple-scroller-version 2025-05-17 12:29:41 -07:00
Elephant Lumps 53e58ce49f remove redundant fragment 2025-05-16 11:01:36 -07:00
Elephant Lumps 08bd431b0e merge conflicts 2025-05-16 11:00:34 -07:00
Elephant Lumps 5e6716645e changeset 2025-05-16 10:55:14 -07:00
Elephant Lumps 8b8a422dc8 fix lint issue 2025-05-16 10:54:50 -07:00
Elephant Lumps be1add1baa add detection for new users for intro component 2025-05-16 10:53:38 -07:00
10 changed files with 210 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Add detection for new users to display special components
+12
View File
@@ -146,8 +146,18 @@ export class Controller {
chatSettings,
shellIntegrationTimeout,
enableCheckpointsSetting,
isNewUser,
taskHistory,
} = await getAllExtensionState(this.context)
const NEW_USER_TASK_COUNT_THRESHOLD = 10
// Check if the user has completed enough tasks to no longer be considered a "new user"
if (isNewUser && !historyItem && taskHistory && taskHistory.length >= NEW_USER_TASK_COUNT_THRESHOLD) {
await updateGlobalState(this.context, "isNewUser", false)
await this.postStateToWebview()
}
if (autoApprovalSettings) {
const updatedAutoApprovalSettings = {
...autoApprovalSettings,
@@ -1357,6 +1367,7 @@ export class Controller {
enableCheckpointsSetting,
globalClineRulesToggles,
shellIntegrationTimeout,
isNewUser,
} = await getAllExtensionState(this.context)
const localClineRulesToggles =
@@ -1399,6 +1410,7 @@ export class Controller {
localCursorRulesToggles: localCursorRulesToggles || {},
workflowToggles: workflowToggles || {},
shellIntegrationTimeout,
isNewUser,
}
}
+1
View File
@@ -88,5 +88,6 @@ export type GlobalStateKey =
| "favoritedModelIds"
| "requestTimeoutMs"
| "shellIntegrationTimeout"
| "isNewUser"
export type LocalStateKey = "localClineRulesToggles"
+3
View File
@@ -76,6 +76,7 @@ async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw: bool
export async function getAllExtensionState(context: vscode.ExtensionContext) {
const [
isNewUser,
storedApiProvider,
apiModelId,
apiKey,
@@ -162,6 +163,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
enableCheckpointsSettingRaw,
mcpMarketplaceEnabledRaw,
] = await Promise.all([
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
getSecret(context, "apiKey") as Promise<string | undefined>,
@@ -354,6 +356,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
favoritedModelIds,
requestTimeoutMs,
},
isNewUser: isNewUser ?? true,
lastShownAnnouncementId,
customInstructions,
taskHistory,
+1
View File
@@ -115,6 +115,7 @@ export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sun
export const DEFAULT_PLATFORM = "unknown"
export interface ExtensionState {
isNewUser: boolean
apiConfiguration?: ApiConfiguration
autoApprovalSettings: AutoApprovalSettings
browserSettings: BrowserSettings
+9 -9
View File
@@ -915,15 +915,6 @@ export const FileServiceDefinition = {
responseStream: false,
options: {},
},
/** Select images from the file system and return as data URLs */
selectImages: {
name: "selectImages",
requestType: EmptyRequest,
requestStream: false,
responseType: StringArray,
responseStream: false,
options: {},
},
/** Opens an image in the system viewer */
openImage: {
name: "openImage",
@@ -960,6 +951,15 @@ export const FileServiceDefinition = {
responseStream: false,
options: {},
},
/** Select images from the file system and return as data URLs */
selectImages: {
name: "selectImages",
requestType: EmptyRequest,
requestStream: false,
responseType: StringArray,
responseStream: false,
options: {},
},
/** Convert URIs to workspace-relative paths */
getRelativePaths: {
name: "getRelativePaths",
+2 -2
View File
@@ -20,11 +20,11 @@ import { checkpointRestore } from "../core/controller/checkpoints/checkpointRest
// File Service
import { openFile } from "../core/controller/file/openFile"
import { selectImages } from "../core/controller/file/selectImages"
import { openImage } from "../core/controller/file/openImage"
import { deleteRuleFile } from "../core/controller/file/deleteRuleFile"
import { createRuleFile } from "../core/controller/file/createRuleFile"
import { searchCommits } from "../core/controller/file/searchCommits"
import { selectImages } from "../core/controller/file/selectImages"
import { getRelativePaths } from "../core/controller/file/getRelativePaths"
import { searchFiles } from "../core/controller/file/searchFiles"
@@ -95,11 +95,11 @@ export function addServices(
// File Service
server.addService(proto.cline.FileService.service, {
openFile: wrapper(openFile, controller),
selectImages: wrapper(selectImages, controller),
openImage: wrapper(openImage, controller),
deleteRuleFile: wrapper(deleteRuleFile, controller),
createRuleFile: wrapper(createRuleFile, controller),
searchCommits: wrapper(searchCommits, controller),
selectImages: wrapper(selectImages, controller),
getRelativePaths: wrapper(getRelativePaths, controller),
searchFiles: wrapper(searchFiles, controller),
})
+7 -1
View File
@@ -34,6 +34,7 @@ import rehypeRemark from "rehype-remark"
import rehypeParse from "rehype-parse"
import HomeHeader from "../welcome/HomeHeader"
import AutoApproveBar from "./auto-approve-menu/AutoApproveBar"
import { SuggestedTasks } from "../welcome/SuggestedTasks"
interface ChatViewProps {
isHidden: boolean
showAnnouncement: boolean
@@ -1061,7 +1062,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
</div>
)}
{!task && <AutoApproveBar />}
{!task && (
<>
<SuggestedTasks />
<AutoApproveBar />
</>
)}
{task && (
<>
@@ -0,0 +1,169 @@
import React, { useState, useEffect, useRef } from "react"
import { TaskServiceClient } from "@/services/grpc-client"
interface Task {
id: string
title: string
description: string
prompt: string
}
const tasks: Task[] = [
{
id: "web-app",
title: "Build a Web App",
description: "Create a modern React app with Vite and Tailwind",
prompt: "Create a landing page for an app where LLMs can swipe on each other. Make it in React with Vite and tailwind, and then test it using the browser tool.",
},
{
id: "cli-tool",
title: "Create a CLI Tool",
description: "Build a Node.js CLI for markdown analysis",
prompt: "Create a Node.js CLI tool that can analyze a directory of markdown files and generate a summary of their contents, including word count, reading time, and most common topics. Include a progress bar for processing files.",
},
{
id: "file-automation",
title: "Automate",
description: "Extract and organize TODO comments",
prompt: "Help me organize my project's documentation by creating a script that finds all TODO comments in the codebase, extracts them into a structured markdown file, and sorts them by priority based on comment content.",
},
]
export const SuggestedTasks: React.FC = () => {
const [currentIndex, setCurrentIndex] = useState(0)
const [isPaused, setIsPaused] = useState(false)
const [direction, setDirection] = useState<"up" | "down">("down") // Track animation direction
const pauseTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const [isUpHovered, setIsUpHovered] = useState(false)
const [isDownHovered, setIsDownHovered] = useState(false)
// Handle task selection
const handleTaskClick = async (prompt: string) => {
await TaskServiceClient.newTask({ text: prompt, images: [] })
}
// Function to handle arrow clicks and navigation
const handleNavigation = (direction: "prev" | "next") => {
// Pause auto-scrolling for 5 seconds
setIsPaused(true)
if (pauseTimeoutRef.current) {
clearTimeout(pauseTimeoutRef.current)
}
pauseTimeoutRef.current = setTimeout(() => {
setIsPaused(false)
}, 2000)
// Set the animation direction
setDirection(direction === "prev" ? "up" : "down")
// Update the current index
if (direction === "next") {
setCurrentIndex((prevIndex) => (prevIndex + 1) % tasks.length)
} else {
setCurrentIndex((prevIndex) => (prevIndex - 1 + tasks.length) % tasks.length)
}
}
// Auto-advance to next task (unless paused)
useEffect(() => {
if (isPaused) return
const interval = setInterval(() => {
setDirection("down") // Default auto-advance direction is down
setCurrentIndex((prevIndex) => (prevIndex + 1) % tasks.length)
}, 3000) // Change task every 3 seconds
return () => clearInterval(interval)
}, [isPaused])
// Clean up pause timeout on unmount
useEffect(() => {
return () => {
if (pauseTimeoutRef.current) {
clearTimeout(pauseTimeoutRef.current)
}
}
}, [])
const currentTask = tasks[currentIndex]
return (
<div className="px-6 py-2 select-none">
{/* Container with fixed height to prevent layout shift */}
<div className="relative h-[80px] sm:h-[100px] mb-1 overflow-hidden">
{/* Fixed navigation arrows (outside of cards) */}
<div className="absolute left-2 top-0 bottom-0 flex flex-col justify-center items-center gap-1 z-20">
{/* Up arrow */}
<div
className="flex items-center justify-center w-5 h-5 rounded-full cursor-pointer transition-colors select-none"
style={{
backgroundColor: isUpHovered
? "var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31))"
: "var(--vscode-editorWidget-background, rgba(60, 60, 60, 0.4))",
}}
onClick={() => handleNavigation("prev")}
onMouseEnter={() => setIsUpHovered(true)}
onMouseLeave={() => setIsUpHovered(false)}>
<span
className="codicon codicon-chevron-up"
style={{
fontSize: "14px",
color: "var(--vscode-foreground, rgba(255, 255, 255, 0.9))",
}}></span>
</div>
{/* Down arrow */}
<div
className="flex items-center justify-center w-5 h-5 rounded-full cursor-pointer transition-colors select-none"
style={{
backgroundColor: isDownHovered
? "var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31))"
: "var(--vscode-editorWidget-background, rgba(60, 60, 60, 0.4))",
}}
onClick={() => handleNavigation("next")}
onMouseEnter={() => setIsDownHovered(true)}
onMouseLeave={() => setIsDownHovered(false)}>
<span
className="codicon codicon-chevron-down"
style={{
fontSize: "14px",
color: "var(--vscode-foreground, rgba(255, 255, 255, 0.9))",
}}></span>
</div>
</div>
{/* Task card with high contrast theme variables */}
<div
key={`task-${currentTask.id}`}
onClick={() => handleTaskClick(currentTask.prompt)}
className="absolute inset-0 flex flex-col px-3 py-2 rounded-lg cursor-pointer select-none
border border-white/30
shadow-lg shadow-black/10 hover:shadow-xl hover:shadow-black/20
active:shadow-md
transition-transform duration-500 ease-out"
style={{
backgroundColor: "var(--vscode-statusBarItem-prominentBackground, var(--vscode-button-background))",
transform: "translateY(0)",
transition: "transform 0.5s ease-out, background-color 0.3s ease",
}}>
{/* Task content (adjusted to make room for left arrows) */}
<div className="relative flex flex-col justify-center flex-1 text-center pl-6">
<h3 className="text-[0.7rem] sm:text-sm md:text-base font-semibold mb-1 sm:mb-2 text-white/95 group-hover:text-white select-none">
{currentTask.title}
</h3>
<p className="text-[0.6rem] sm:text-xs md:text-sm text-white/90 line-clamp-2 break-words leading-tight mx-auto select-none">
{currentTask.description}
</p>
</div>
{/* Paper airplane icon (center-right) */}
<div
className="absolute right-2 sm:right-2.5 top-1/2 transform -translate-y-1/2 w-3 sm:w-3.5 h-3 sm:h-3.5 opacity-70 hover:opacity-100
transition-opacity duration-300 ease-out">
<span className="codicon codicon-send text-white/90" style={{ fontSize: "14px" }}></span>
</div>
</div>
</div>
</div>
)
}
@@ -80,6 +80,7 @@ export const ExtensionStateContextProvider: React.FC<{
localWindsurfRulesToggles: {},
workflowToggles: {},
shellIntegrationTimeout: 4000, // default timeout for shell integration
isNewUser: false,
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)