mirror of
https://github.com/cline/cline.git
synced 2026-10-31 02:42:02 +08:00
feat(webview): add dedicated Cline Kanban launch modal
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
webview-ui/src/assets/cline_kanban_demo.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
* text=auto eol=lf
|
||||
|
||||
@@ -115,6 +115,7 @@ export abstract class WebviewProvider {
|
||||
font-src ${this.getCspSource()} data:;
|
||||
style-src ${this.getCspSource()} 'unsafe-inline';
|
||||
img-src ${this.getCspSource()} https: data:;
|
||||
media-src ${this.getCspSource()} https: data: blob:;
|
||||
script-src 'nonce-${nonce}' 'unsafe-eval';">
|
||||
<title>Cline</title>
|
||||
</head>
|
||||
@@ -202,6 +203,7 @@ export abstract class WebviewProvider {
|
||||
`font-src ${this.getCspSource()}`,
|
||||
`style-src ${this.getCspSource()} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
|
||||
`img-src ${this.getCspSource()} https: data:`,
|
||||
`media-src ${this.getCspSource()} https: data: blob: http://${localServerUrl} http://0.0.0.0:${localPort}`,
|
||||
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
|
||||
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
|
||||
]
|
||||
|
||||
+55
-14
@@ -1,7 +1,8 @@
|
||||
import type { Boolean, EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { useEffect } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import AccountView from "./components/account/AccountView"
|
||||
import ChatView from "./components/chat/ChatView"
|
||||
import ClineKanbanLaunchModal, { CLINE_KANBAN_MODAL_DISMISS_ID } from "./components/common/ClineKanbanLaunchModal"
|
||||
import HistoryView from "./components/history/HistoryView"
|
||||
import McpView from "./components/mcp/configuration/McpConfigurationView"
|
||||
import OnboardingView from "./components/onboarding/OnboardingView"
|
||||
@@ -11,13 +12,14 @@ import WorktreesView from "./components/worktrees/WorktreesView"
|
||||
import { useClineAuth } from "./context/ClineAuthContext"
|
||||
import { useExtensionState } from "./context/ExtensionStateContext"
|
||||
import { Providers } from "./Providers"
|
||||
import { UiServiceClient } from "./services/grpc-client"
|
||||
import { StateServiceClient, UiServiceClient } from "./services/grpc-client"
|
||||
|
||||
const AppContent = () => {
|
||||
const {
|
||||
didHydrateState,
|
||||
showWelcome,
|
||||
shouldShowAnnouncement,
|
||||
dismissedBanners,
|
||||
showMcp,
|
||||
mcpTab,
|
||||
showSettings,
|
||||
@@ -37,23 +39,61 @@ const AppContent = () => {
|
||||
hideWorktrees,
|
||||
hideAnnouncement,
|
||||
} = useExtensionState()
|
||||
const [showKanbanModal, setShowKanbanModal] = useState(false)
|
||||
const [hasShownKanbanModal, setHasShownKanbanModal] = useState(false)
|
||||
|
||||
const { clineUser, organizations, activeOrganization } = useClineAuth()
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowAnnouncement) {
|
||||
setShowAnnouncement(true)
|
||||
const showUpdateAnnouncementModal = useCallback(() => {
|
||||
setShowAnnouncement(true)
|
||||
UiServiceClient.onDidShowAnnouncement({} as EmptyRequest)
|
||||
.then((response: Boolean) => {
|
||||
setShouldShowAnnouncement(response.value)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to acknowledge announcement:", error)
|
||||
})
|
||||
}, [setShouldShowAnnouncement, setShowAnnouncement])
|
||||
|
||||
// Use the gRPC client instead of direct WebviewMessage
|
||||
UiServiceClient.onDidShowAnnouncement({} as EmptyRequest)
|
||||
.then((response: Boolean) => {
|
||||
setShouldShowAnnouncement(response.value)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to acknowledge announcement:", error)
|
||||
})
|
||||
useEffect(() => {
|
||||
if (!didHydrateState || showWelcome || hasShownKanbanModal) {
|
||||
return
|
||||
}
|
||||
}, [shouldShowAnnouncement, setShouldShowAnnouncement, setShowAnnouncement])
|
||||
const hasDismissedKanbanModal = dismissedBanners?.some((banner) => banner.bannerId === CLINE_KANBAN_MODAL_DISMISS_ID)
|
||||
if (!hasDismissedKanbanModal) {
|
||||
setShowKanbanModal(true)
|
||||
}
|
||||
setHasShownKanbanModal(true)
|
||||
}, [didHydrateState, dismissedBanners, hasShownKanbanModal, showWelcome])
|
||||
|
||||
// Keep update announcements queued until the Kanban modal has either shown and closed or been skipped.
|
||||
useEffect(() => {
|
||||
if (!didHydrateState || showWelcome || !shouldShowAnnouncement || showAnnouncement) {
|
||||
return
|
||||
}
|
||||
const isKanbanModalBlocking = showKanbanModal || !hasShownKanbanModal
|
||||
if (isKanbanModalBlocking) {
|
||||
return
|
||||
}
|
||||
showUpdateAnnouncementModal()
|
||||
}, [
|
||||
didHydrateState,
|
||||
showWelcome,
|
||||
shouldShowAnnouncement,
|
||||
showAnnouncement,
|
||||
showKanbanModal,
|
||||
hasShownKanbanModal,
|
||||
showUpdateAnnouncementModal,
|
||||
])
|
||||
|
||||
const handleCloseKanbanModal = useCallback((doNotShowAgain: boolean) => {
|
||||
setShowKanbanModal(false)
|
||||
if (doNotShowAgain) {
|
||||
StateServiceClient.dismissBanner({ value: CLINE_KANBAN_MODAL_DISMISS_ID }).catch((error) =>
|
||||
console.error("Failed to persist Cline Kanban modal dismissal:", error),
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!didHydrateState) {
|
||||
return null
|
||||
@@ -65,6 +105,7 @@ const AppContent = () => {
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<ClineKanbanLaunchModal onClose={handleCloseKanbanModal} open={showKanbanModal} />
|
||||
{showSettings && <SettingsView onDone={hideSettings} targetSection={settingsTargetSection} />}
|
||||
{showHistory && <HistoryView onDone={hideHistory} />}
|
||||
{showMcp && <McpView initialTab={mcpTab} onDone={closeMcpView} />}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fa7c90bf657f317e63dee745e010898ca0cb13ee82e9dbe341ac16199170399a
|
||||
size 3738635
|
||||
@@ -0,0 +1,121 @@
|
||||
import { StringRequest } from "@shared/proto/cline/common"
|
||||
import { VSCodeButton, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import kanbanDemoVideo from "@/assets/cline_kanban_demo.mp4"
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
|
||||
import { FileServiceClient, StateServiceClient } from "@/services/grpc-client"
|
||||
|
||||
const INSTALL_COMMAND = "npm install -g cline"
|
||||
const COPIED_TIMEOUT = 1500
|
||||
const kanbanDemoVideoSrc = kanbanDemoVideo.startsWith("/src/")
|
||||
? new URL(kanbanDemoVideo, import.meta.url).toString()
|
||||
: kanbanDemoVideo
|
||||
|
||||
export const CLINE_KANBAN_MODAL_DISMISS_ID = "cline-kanban-launch-modal-v1"
|
||||
|
||||
interface ClineKanbanLaunchModalProps {
|
||||
open: boolean
|
||||
onClose: (doNotShowAgain: boolean) => void
|
||||
}
|
||||
|
||||
export const ClineKanbanLaunchModal: React.FC<ClineKanbanLaunchModalProps> = ({ open, onClose }) => {
|
||||
const [doNotShowAgain, setDoNotShowAgain] = useState(false)
|
||||
const [isInstalling, setIsInstalling] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const isVsCode = useMemo(() => PLATFORM_CONFIG.type === PlatformType.VSCODE, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setCopied(false)
|
||||
setIsInstalling(false)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleAction = async () => {
|
||||
if (isVsCode) {
|
||||
setIsInstalling(true)
|
||||
try {
|
||||
await StateServiceClient.installClineCli({})
|
||||
} catch (error) {
|
||||
console.error("Failed to run CLI install command:", error)
|
||||
} finally {
|
||||
setIsInstalling(false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await FileServiceClient.copyToClipboard(StringRequest.create({ value: INSTALL_COMMAND }))
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), COPIED_TIMEOUT)
|
||||
} catch (error) {
|
||||
console.error("Failed to copy CLI install command:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={(isOpen) => !isOpen && onClose(doNotShowAgain)} open={open}>
|
||||
<DialogContent
|
||||
aria-describedby="cline-kanban-description"
|
||||
aria-labelledby="cline-kanban-title"
|
||||
className="pt-4 px-5 pb-4 gap-0 max-w-2xl">
|
||||
<div className="space-y-3" id="cline-kanban-description">
|
||||
<div className="pr-6 min-h-6 flex items-center">
|
||||
<h2
|
||||
className="m-0 text-lg font-semibold"
|
||||
id="cline-kanban-title"
|
||||
style={{ color: "var(--vscode-editor-foreground)" }}>
|
||||
Introducing Cline Kanban
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<video
|
||||
autoPlay
|
||||
className="w-full rounded-md border border-[var(--vscode-editorGroup-border)]"
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
src={kanbanDemoVideoSrc}
|
||||
/>
|
||||
|
||||
<p className="text-sm" style={{ color: "var(--vscode-descriptionForeground)" }}>
|
||||
A replacement for your IDE better suited for running many agents in parallel and reviewing diffs. Enable
|
||||
auto-commit and link cards together to create dependency chains that complete large amounts of work
|
||||
autonomously.
|
||||
</p>
|
||||
|
||||
<div className="p-1">
|
||||
<code className="block rounded-sm px-2 py-1 bg-[var(--vscode-textCodeBlock-background)] text-sm">
|
||||
{INSTALL_COMMAND}
|
||||
</code>
|
||||
<div className="mt-3">
|
||||
<VSCodeButton disabled={isInstalling} onClick={handleAction}>
|
||||
{isVsCode
|
||||
? isInstalling
|
||||
? "Running install command..."
|
||||
: "Run in terminal"
|
||||
: copied
|
||||
? "Copied"
|
||||
: "Copy command"}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<VSCodeCheckbox
|
||||
checked={doNotShowAgain}
|
||||
onChange={(e: any) => {
|
||||
setDoNotShowAgain(e.target.checked === true)
|
||||
}}>
|
||||
Do not show again
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClineKanbanLaunchModal
|
||||
@@ -38,6 +38,7 @@ if (!VALID_PLATFORMS.includes(platform)) {
|
||||
console.log("Building webview for", platform)
|
||||
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
optimizeDeps: {
|
||||
force: true, // Forces re-optimization
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user