Skip to content

creator #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions creatormodetask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Creator Mode Task Description

## Overview

This project is an **agentic AI extension** for VSCode, forked from **RooCode**, which itself is a fork of **Cline**. Our fork is called **PearAI-Roo-Code**.

## Goal

We are implementing a **new view called Creator Mode** into this extension. The objective is to introduce a **separate GUI file structure** to minimize merge conflicts when pulling upstream. However, separation is not a strict requirement—if it makes more sense to integrate changes within the existing structure, we will do so.

## Guidelines

- **Maintain Separation:** GUI files should be kept separate to minimize merge conflicts when pulling upstream.
- **Flexible Code Organization:** While separation is preferred, prioritize maintainability and efficiency over strict separation.
- **Keep This Document Updated:** Ensure this file always reflects the latest task status for continuity.
- **Clear and Concise Development:** No unnecessary complexity—keep solutions to the point and functional.
- no bs.
- dont hallucinate.

## Creator Mode Details

- Creator Mode will feature a **text input box** at the center, allowing users to enter prompts.
- The design will differ from the existing **ChatView** (`chatview.tsx`), but function similarly in terms of input handling.
- When the user enters a prompt, it will be sent to the **selected AI model**.
- The response will generate a **new file containing an action plan**, which the user can edit directly.
- A new **mode identifier** (`Creator Mode`) will be introduced, similar to the existing **Ask Mode** and **Architect Mode**.

## Development Plan (Step-by-Step)

[COMPLETED] - add completed tasks here

[NEXT STEPS]

1. **Initial Input Box Implementation**

- Create a simple input box that sends user input to the selected model.
- Ensure basic communication between the UI and backend.
- Validate that the model receives and processes input correctly.

2. **Introduce Creator Mode**

- Understand how existing "ask" mode and "architect" mode are implemented.
- Add `Creator Mode` to the list of available modes.
- Ensure the UI adapts accordingly when this mode is selected.

3. **Implement File Edit Functionality**
- Modify the response handling to generate an editable file.
- Provide a smooth user experience for modifying action plans.

- Proceed step-by-step, ensuring each milestone is functional before progressing, so begin with step **1: Implementing the input box** and verifying AI model communication, once stable, move on to **step 2: introducing Creator Mode**.

This file should be regularly updated to reflect the project's current state and objectives.
4 changes: 2 additions & 2 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterComman
// deserialize cached webview, but since we use retainContextWhenHidden, we
// don't need to use that event).
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
const tabProvider = new ClineProvider(context, outputChannel)
const tabProvider = new ClineProvider(context, outputChannel, true)
// const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))

Expand Down Expand Up @@ -82,5 +82,5 @@ const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterComman

// Lock the editor group so clicking on files doesn't open them over the panel
await delay(100)
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
// await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
}
9 changes: 9 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ export class ClineProvider implements vscode.WebviewViewProvider {
private latestAnnouncementId = "jan-21-2025-custom-modes" // update to some unique identifier when we add a new announcement
configManager: ConfigManager
customModesManager: CustomModesManager
private isCreator: boolean = false

constructor(
readonly context: vscode.ExtensionContext,
private readonly outputChannel: vscode.OutputChannel,
isCreator: boolean = false,
) {
this.outputChannel.appendLine(`creator = ${isCreator}`)
this.isCreator = isCreator
console.dir("CREATOR")
console.dir(this.isCreator)
this.outputChannel.appendLine("ClineProvider instantiated")
ClineProvider.activeInstances.add(this)
this.workspaceTracker = new WorkspaceTracker(this)
Expand Down Expand Up @@ -414,6 +420,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
window.isCreator="${this.isCreator}";
</script>
`

Expand All @@ -426,6 +433,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort} http://localhost:8000 http://0.0.0.0:8000 https://stingray-app-gb2an.ondigitalocean.app`,
]

console.dir("CREATORRRRRR")
console.dir(this.isCreator)
return /*html*/ `
<!DOCTYPE html>
<html lang="en">
Expand Down
6 changes: 3 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function activate(context: vscode.ExtensionContext) {
context.globalState.update("allowedCommands", defaultCommands)
}

const sidebarProvider = new ClineProvider(context, outputChannel)
const sidebarProvider = new ClineProvider(context, outputChannel, false)

context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, sidebarProvider, {
Expand Down Expand Up @@ -111,7 +111,7 @@ export function activate(context: vscode.ExtensionContext) {
outputChannel.appendLine("Opening Roo Code in new tab")
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
const tabProvider = new ClineProvider(context, outputChannel)
const tabProvider = new ClineProvider(context, outputChannel, true)
//const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))

Expand All @@ -137,7 +137,7 @@ export function activate(context: vscode.ExtensionContext) {

// Lock the editor group so clicking on files doesn't open them over the panel
await delay(100)
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
// await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
}

// context.subscriptions.push(vscode.commands.registerCommand("roo-cline.popoutButtonClicked", openClineInNewTab))
Expand Down
2 changes: 2 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface ExtensionMessage {
| "updateCustomMode"
| "deleteCustomMode"
| "currentCheckpointUpdated"
| "creator"
text?: string
action?:
| "chatButtonClicked"
Expand All @@ -53,6 +54,7 @@ export interface ExtensionMessage {
| "historyButtonClicked"
| "promptsButtonClicked"
| "didBecomeVisible"
| "generateActionPlan"
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState
images?: string[]
Expand Down
2 changes: 2 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export interface WebviewMessage {
| "openPearAiAuth"
| "deleteMcpServer"
| "maxOpenTabsContext"
| "creator"
| "generateActionPlan"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse
Expand Down
9 changes: 9 additions & 0 deletions src/shared/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ export function getToolsForMode(groups: readonly GroupEntry[]): string[] {

// Main modes configuration as an ordered array
export const modes: readonly ModeConfig[] = [
{
slug: "creator",
name: "Creator",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), a creative and systematic software architect focused on turning high-level ideas into actionable plans. Your primary goal is to help users transform their ideas into structured action plans.",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"Your role is to help users transform their ideas into concrete action plans. When a user provides a prompt, analyze it carefully and create a detailed, step-by-step plan that outlines how to implement their idea. Focus on breaking down complex tasks into manageable steps, considering technical requirements, potential challenges, and best practices. The plan should be clear enough that it can be directly implemented by switching to Code mode afterward.",
},
{
slug: "code",
name: "Code",
Expand Down
7 changes: 4 additions & 3 deletions webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import WelcomeView from "./components/welcome/WelcomeView"
import McpView from "./components/mcp/McpView"
import PromptsView from "./components/prompts/PromptsView"
import { Inspector } from "react-dev-inspector"
import Creator from "./creator/creator"

type Tab = "settings" | "history" | "mcp" | "prompts" | "chat"

Expand Down Expand Up @@ -83,10 +84,10 @@ const App = () => {
)
}

const tempIsCreator: boolean = true

const AppWithProviders = () => (
<ExtensionStateContextProvider>
<App />
</ExtensionStateContextProvider>
<ExtensionStateContextProvider>{window.isCreator === "true" ? <Creator /> : <App />}</ExtensionStateContextProvider>
)

export default AppWithProviders
2 changes: 1 addition & 1 deletion webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
<img src={splashIcon} alt="..." />
<div className="w-[300px] flex-col justify-start items-start gap-5 inline-flex">
<div className="flex flex-col text-left">
<div className="text-2xl">PearAI Coding Agent</div>
<div className="text-2xl">PearAI Casdfoding Agent</div>
<div className="h-[18px] opacity-50 text-xs leading-[18px]">
Powered by Roo Code / Cline
</div>
Expand Down
189 changes: 189 additions & 0 deletions webview-ui/src/creator/creator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { useState, useCallback, useEffect, useRef } from "react"
import { vscode } from "../utils/vscode"
import { WebviewMessage } from "../../../src/shared/WebviewMessage"
import { ExtensionMessage, ClineMessage } from "../../../src/shared/ExtensionMessage"
import { Markdown } from "../components/ui/markdown"

interface Message {
role: "user" | "assistant"
content: string
timestamp: number
}

const Creator = () => {
const [prompt, setPrompt] = useState("")
const [isProcessing, setIsProcessing] = useState(false)
const [response, setResponse] = useState<ClineMessage | null>(null)
const [messages, setMessages] = useState<Message[]>([])
const [isLoading, setIsLoading] = useState(false)
const [inputValue, setInputValue] = useState("")
const messagesEndRef = useRef<HTMLDivElement>(null)

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!prompt.trim() || isProcessing) return

setIsProcessing(true)
try {
const message: WebviewMessage = {
type: "creator",
text: prompt.trim(),
}
await vscode.postMessage(message)
setPrompt("")
} finally {
setIsProcessing(false)
}
}

const handleMessage = useCallback((e: MessageEvent<ExtensionMessage>) => {
const message = e.data
if (message.type === "partialMessage" && message.partialMessage) {
setResponse(message.partialMessage)
}
}, [])

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}

useEffect(() => {
scrollToBottom()
}, [messages])

const handleSendMessage = useCallback((text: string) => {
if (!text.trim()) return

// Add user message to chat
setMessages((prev) => [
...prev,
{
role: "user",
content: text.trim(),
timestamp: Date.now(),
},
])

setIsLoading(true)

// Send message to extension
vscode.postMessage({
type: "newTask",
text: text.trim(),
})
}, [])

// Add message event listener
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const message = event.data

if (message.type === "partialMessage" && message.partialMessage) {
// Handle streaming response
setMessages((prev) => {
const lastMessage = prev[prev.length - 1]
if (lastMessage?.role === "assistant") {
// Update existing assistant message
return [
...prev.slice(0, -1),
{
...lastMessage,
content: lastMessage.content + message.partialMessage.text,
},
]
} else {
// Add new assistant message
return [
...prev,
{
role: "assistant",
content: message.partialMessage.text || "",
timestamp: Date.now(),
},
]
}
})
setIsLoading(false)
}
}

window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
}, [])

return (
<div className="flex flex-col h-screen p-4 bg-background text-foreground">
{/* Messages container with scrolling */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((msg, i) => (
<div
key={msg.timestamp}
className={`max-w-[80%] rounded-lg p-4 animate-fade-in transition-all duration-200 ease-in-out hover:shadow-lg ${
msg.role === "user"
? "ml-auto bg-[#E64C9E] text-white hover:bg-[#D33C8E]"
: "bg-editor-background border border-input-border hover:border-[#E64C9E]"
}`}>
{/* Use Markdown component for messages */}
<Markdown content={msg.content} />
</div>
))}

{isLoading && (
<div className="text-muted-foreground text-center py-4 animate-pulse">
<div className="flex items-center justify-center gap-2">
<div className="w-2 h-2 bg-[#E64C9E] rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-[#E64C9E] rounded-full animate-bounce delay-100"></div>
<div className="w-2 h-2 bg-[#E64C9E] rounded-full animate-bounce delay-200"></div>
</div>
</div>
)}

{/* Scroll anchor */}
<div ref={messagesEndRef} />
</div>

{/* Input container */}
<div className="flex items-end gap-4 mt-auto p-4 bg-editor-background rounded-lg border border-input-border">
<div className="flex-1">
<textarea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSendMessage(inputValue)
setInputValue("")
}
}}
placeholder="Type your message..."
rows={1}
className="w-full px-4 py-2 rounded-lg bg-input-background text-input-foreground border-none focus:outline-none focus:ring-2 focus:ring-[#E64C9E] resize-none"
/>
</div>
<button
onClick={() => {
handleSendMessage(inputValue)
setInputValue("")
}}
disabled={isLoading}
className="px-6 py-2 bg-[#E64C9E] text-white rounded-lg flex items-center gap-2 hover:bg-[#D33C8E] disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ease-in-out transform hover:scale-105 active:scale-95">
<span>Send</span>
<svg
className="w-4 h-4 transition-transform duration-200 group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M14 5l7 7m0 0l-7 7m7-7H3"
/>
</svg>
</button>
</div>
</div>
)
}

export default Creator
Loading
Loading