Skip to content

Commit 5c43d39

Browse files
committed
Add UI and fix build
1 parent 6908d3e commit 5c43d39

File tree

6 files changed

+133
-5
lines changed

6 files changed

+133
-5
lines changed

src/core/prompts/sections/schedulable-rules.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,15 @@ export class SchedulableRulesManager {
136136

137137
logger.debug(`Loaded rule file: ${file}, interval: ${display}`)
138138
} catch (err) {
139-
logger.error(`Failed to parse schedulable rule file ${file}:`, err)
139+
logger.error(
140+
`Failed to parse schedulable rule file ${file}: ${err instanceof Error ? err.message : String(err)}`,
141+
)
140142
}
141143
}
142144

143145
return rules
144146
} catch (err) {
145-
logger.error("Failed to load schedulable rules:", err)
147+
logger.error(`Failed to load schedulable rules: ${err instanceof Error ? err.message : String(err)}`)
146148
return []
147149
}
148150
}

src/core/webview/ClineProvider.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Mode, PromptComponent, defaultModeSlug, ModeConfig } from "../../shared
2222
import { checkExistKey } from "../../shared/checkExistApiConfig"
2323
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
2424
import { TERMINAL_OUTPUT_LIMIT } from "../../shared/terminal"
25+
import { TelemetrySetting } from "../../shared/TelemetrySetting"
2526
import { downloadTask } from "../../integrations/misc/export-markdown"
2627
import { openFile, openImage } from "../../integrations/misc/open-file"
2728
import { selectImages } from "../../integrations/misc/process-images"
@@ -56,7 +57,7 @@ import { openMention } from "../mentions"
5657
import { getNonce } from "./getNonce"
5758
import { getUri } from "./getUri"
5859
import { telemetryService } from "../../services/telemetry/TelemetryService"
59-
import { TelemetrySetting } from "../../shared/TelemetrySetting"
60+
import { SchedulableRulesManager } from "../prompts/sections/schedulable-rules"
6061

6162
/**
6263
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -73,6 +74,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
7374
private clineStack: Cline[] = []
7475
private workspaceTracker?: WorkspaceTracker
7576
protected mcpHub?: McpHub // Change from private to protected
77+
private schedulableRulesManager: SchedulableRulesManager
7678
private latestAnnouncementId = "mar-7-2025-3-8" // update to some unique identifier when we add a new announcement
7779
private contextProxy: ContextProxy
7880
configManager: ConfigManager
@@ -90,6 +92,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
9092
// Register this provider with the telemetry service to enable it to add properties like mode and provider
9193
telemetryService.setProvider(this)
9294

95+
// Initialize SchedulableRulesManager
96+
this.schedulableRulesManager = new SchedulableRulesManager()
97+
this.outputChannel.appendLine("SchedulableRulesManager initialized")
98+
9399
this.workspaceTracker = new WorkspaceTracker(this)
94100
this.configManager = new ConfigManager(this.context)
95101
this.customModesManager = new CustomModesManager(this.context, async () => {
@@ -1963,6 +1969,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
19631969
experiments,
19641970
enableMcpServerCreation,
19651971
rooIgnoreInstructions,
1972+
this.schedulableRulesManager,
19661973
)
19671974
return systemPrompt
19681975
}
@@ -2335,6 +2342,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
23352342
telemetrySetting,
23362343
showRooIgnoredFiles,
23372344
} = await this.getState()
2345+
2346+
// Get schedulable rules data
2347+
// Get schedulable rules data
2348+
let schedulableRules = await this.schedulableRulesManager.getAllRules(
2349+
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) || "",
2350+
)
23382351
const telemetryKey = process.env.POSTHOG_API_KEY
23392352
const machineId = vscode.env.machineId
23402353

@@ -2353,6 +2366,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
23532366
alwaysAllowMcp: alwaysAllowMcp ?? false,
23542367
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
23552368
alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
2369+
schedulableRules,
23562370
uriScheme: vscode.env.uriScheme,
23572371
currentTaskItem: this.getCurrentCline()?.taskId
23582372
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)

src/shared/ExtensionMessage.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface ExtensionMessage {
5454
| "browserToolEnabled"
5555
| "browserConnectionResult"
5656
| "remoteBrowserEnabled"
57+
| "schedulableRules"
5758
text?: string
5859
action?:
5960
| "chatButtonClicked"
@@ -83,6 +84,12 @@ export interface ExtensionMessage {
8384
mcpServers?: McpServer[]
8485
commits?: GitCommit[]
8586
listApiConfig?: ApiConfigMeta[]
87+
schedulableRules?: Array<{
88+
fileName: string
89+
displayInterval: string
90+
nextExecution: number
91+
lastExecuted: number
92+
}>
8693
mode?: Mode
8794
customMode?: ModeConfig
8895
slug?: string
@@ -150,6 +157,12 @@ export interface ExtensionState {
150157
telemetryKey?: string
151158
machineId?: string
152159
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
160+
schedulableRules?: Array<{
161+
fileName: string
162+
displayInterval: string
163+
nextExecution: number
164+
lastExecuted: number
165+
}>
153166
}
154167

155168
export type { ClineMessage, ClineAsk, ClineSay }

webview-ui/src/components/prompts/PromptsView.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from "@/components/ui"
2424
import { cn } from "@/lib/utils"
2525
import { useExtensionState } from "../../context/ExtensionStateContext"
26+
import SchedulableRulesSection from "./SchedulableRulesSection"
2627
import {
2728
Mode,
2829
PromptComponent,
@@ -54,8 +55,8 @@ type PromptsViewProps = {
5455
}
5556

5657
// Helper to get group name regardless of format
57-
function getGroupName(group: GroupEntry): ToolGroup {
58-
return Array.isArray(group) ? group[0] : group
58+
function getGroupName(group: GroupEntry): string {
59+
return Array.isArray(group) ? group[0] : (group as string)
5960
}
6061

6162
const PromptsView = ({ onDone }: PromptsViewProps) => {
@@ -74,6 +75,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
7475
customModes,
7576
enableCustomModeCreation,
7677
setEnableCustomModeCreation,
78+
schedulableRules,
7779
} = useExtensionState()
7880

7981
// Memoize modes to preserve array order
@@ -949,6 +951,11 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
949951
</div>
950952
</div>
951953

954+
{/* Schedulable Rules Section */}
955+
{schedulableRules && schedulableRules.length > 0 && (
956+
<SchedulableRulesSection rules={schedulableRules} />
957+
)}
958+
952959
<div
953960
style={{
954961
paddingBottom: "40px",
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { useState } from "react"
2+
import { vscode } from "../../utils/vscode"
3+
4+
type SchedulableRule = {
5+
fileName: string
6+
displayInterval: string
7+
nextExecution: number
8+
}
9+
10+
type SchedulableRulesSectionProps = {
11+
rules: SchedulableRule[]
12+
}
13+
14+
const SchedulableRulesSection: React.FC<SchedulableRulesSectionProps> = ({ rules }) => {
15+
const [isExpanded, setIsExpanded] = useState(true)
16+
17+
const formatTimeRemaining = (ms: number): string => {
18+
if (ms <= 0) return "Now"
19+
20+
const seconds = Math.floor(ms / 1000)
21+
const minutes = Math.floor(seconds / 60)
22+
const hours = Math.floor(minutes / 60)
23+
const days = Math.floor(hours / 24)
24+
25+
if (days > 0) return `${days}d ${hours % 24}h`
26+
if (hours > 0) return `${hours}h ${minutes % 60}m`
27+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
28+
return `${seconds}s`
29+
}
30+
31+
if (rules.length === 0) return null
32+
33+
return (
34+
<div className="py-5 border-b border-vscode-input-border">
35+
<div className="flex justify-between items-center mb-3">
36+
<div onClick={() => setIsExpanded(!isExpanded)} className="cursor-pointer flex items-center">
37+
<span className={`codicon codicon-${isExpanded ? "chevron-down" : "chevron-right"} mr-2`}></span>
38+
<h3 className="text-vscode-foreground m-0">Schedulable Rules</h3>
39+
</div>
40+
</div>
41+
42+
{isExpanded && (
43+
<>
44+
<div className="text-sm text-vscode-descriptionForeground mb-3">
45+
Rules that are automatically applied at specified time intervals. Click a rule to edit it.
46+
</div>
47+
48+
<div className="space-y-2">
49+
{rules.map((rule) => (
50+
<div
51+
key={rule.fileName}
52+
className="flex justify-between items-center p-2 hover:bg-vscode-list-hoverBackground rounded cursor-pointer"
53+
onClick={() => {
54+
vscode.postMessage({
55+
type: "openFile",
56+
text: `./${rule.fileName}`,
57+
values: {
58+
create: false,
59+
},
60+
})
61+
}}>
62+
<div>
63+
<div className="font-medium">{rule.fileName}</div>
64+
<div className="text-xs text-vscode-descriptionForeground">
65+
Every {rule.displayInterval}
66+
</div>
67+
</div>
68+
<div className="flex items-center">
69+
<span className="text-xs bg-vscode-badge-background text-vscode-badge-foreground rounded-full px-2 py-1">
70+
Next: {formatTimeRemaining(rule.nextExecution)}
71+
</span>
72+
</div>
73+
</div>
74+
))}
75+
</div>
76+
</>
77+
)}
78+
</div>
79+
)
80+
}
81+
82+
export default SchedulableRulesSection

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ export interface ExtensionStateContextType extends ExtensionState {
7878
remoteBrowserEnabled?: boolean
7979
setRemoteBrowserEnabled: (value: boolean) => void
8080
machineId?: string
81+
schedulableRules?: Array<{
82+
fileName: string
83+
displayInterval: string
84+
nextExecution: number
85+
lastExecuted: number
86+
}>
8187
}
8288

8389
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@@ -208,6 +214,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
208214
setCurrentCheckpoint(message.text)
209215
break
210216
}
217+
case "schedulableRules": {
218+
setState((prevState) => ({ ...prevState, schedulableRules: message.schedulableRules }))
219+
break
220+
}
211221
case "listApiConfig": {
212222
setListApiConfigMeta(message.listApiConfig ?? [])
213223
break

0 commit comments

Comments
 (0)