|
|
@@ -3,7 +3,6 @@ import debounce from "debounce"
|
|
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"
|
|
|
import { useDeepCompareEffect, useEvent, useMount } from "react-use"
|
|
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
|
|
|
-import styled from "styled-components"
|
|
|
import removeMd from "remove-markdown"
|
|
|
import { Trans } from "react-i18next"
|
|
|
|
|
|
@@ -30,8 +29,8 @@ import { useAppTranslation } from "@src/i18n/TranslationContext"
|
|
|
|
|
|
import TelemetryBanner from "../common/TelemetryBanner"
|
|
|
import HistoryPreview from "../history/HistoryPreview"
|
|
|
-import RooHero from "../welcome/RooHero"
|
|
|
-
|
|
|
+import RooHero from "@src/components/welcome/RooHero"
|
|
|
+import RooTips from "@src/components/welcome/RooTips"
|
|
|
import Announcement from "./Announcement"
|
|
|
import BrowserSessionRow from "./BrowserSessionRow"
|
|
|
import ChatRow from "./ChatRow"
|
|
|
@@ -39,12 +38,12 @@ import ChatTextArea from "./ChatTextArea"
|
|
|
import TaskHeader from "./TaskHeader"
|
|
|
import AutoApproveMenu from "./AutoApproveMenu"
|
|
|
import SystemPromptWarning from "./SystemPromptWarning"
|
|
|
+import { useTaskSearch } from "../history/useTaskSearch"
|
|
|
|
|
|
export interface ChatViewProps {
|
|
|
isHidden: boolean
|
|
|
showAnnouncement: boolean
|
|
|
hideAnnouncement: () => void
|
|
|
- showHistoryView: () => void
|
|
|
}
|
|
|
|
|
|
export interface ChatViewRef {
|
|
|
@@ -56,13 +55,12 @@ export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
|
|
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
|
|
|
|
|
const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewProps> = (
|
|
|
- { isHidden, showAnnouncement, hideAnnouncement, showHistoryView },
|
|
|
+ { isHidden, showAnnouncement, hideAnnouncement },
|
|
|
ref,
|
|
|
) => {
|
|
|
const { t } = useAppTranslation()
|
|
|
const modeShortcutText = `${isMac ? "⌘" : "Ctrl"} + . ${t("chat:forNextMode")}`
|
|
|
const {
|
|
|
- version,
|
|
|
clineMessages: messages,
|
|
|
taskHistory,
|
|
|
apiConfiguration,
|
|
|
@@ -84,8 +82,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
customModes,
|
|
|
telemetrySetting,
|
|
|
hasSystemPromptOverride,
|
|
|
+ historyPreviewCollapsed, // Added historyPreviewCollapsed
|
|
|
} = useExtensionState()
|
|
|
|
|
|
+ const { tasks } = useTaskSearch()
|
|
|
+
|
|
|
+ // Initialize expanded state based on the persisted setting (default to expanded if undefined)
|
|
|
+ const [isExpanded, setIsExpanded] = useState(
|
|
|
+ historyPreviewCollapsed === undefined ? true : !historyPreviewCollapsed,
|
|
|
+ )
|
|
|
+
|
|
|
+ const toggleExpanded = useCallback(() => {
|
|
|
+ const newState = !isExpanded
|
|
|
+ setIsExpanded(newState)
|
|
|
+ // Send message to extension to persist the new collapsed state
|
|
|
+ vscode.postMessage({ type: "setHistoryPreviewCollapsed", bool: !newState })
|
|
|
+ }, [isExpanded])
|
|
|
+
|
|
|
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
|
|
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
|
|
|
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
|
|
|
@@ -1197,17 +1210,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
}))
|
|
|
|
|
|
return (
|
|
|
- <div
|
|
|
- style={{
|
|
|
- position: "fixed",
|
|
|
- top: 0,
|
|
|
- left: 0,
|
|
|
- right: 0,
|
|
|
- bottom: 0,
|
|
|
- display: isHidden ? "none" : "flex",
|
|
|
- flexDirection: "column",
|
|
|
- overflow: "hidden",
|
|
|
- }}>
|
|
|
+ <div className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
|
|
|
+ {showAnnouncement && <Announcement hideAnnouncement={hideAnnouncement} />}
|
|
|
{task ? (
|
|
|
<>
|
|
|
<TaskHeader
|
|
|
@@ -1237,20 +1241,43 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
)}
|
|
|
</>
|
|
|
) : (
|
|
|
- <div
|
|
|
- style={{
|
|
|
- flex: "1 1 0", // flex-grow: 1, flex-shrink: 1, flex-basis: 0
|
|
|
- minHeight: 0,
|
|
|
- overflowY: "auto",
|
|
|
- display: "flex",
|
|
|
- flexDirection: "column",
|
|
|
- paddingBottom: "10px",
|
|
|
- }}>
|
|
|
- {telemetrySetting === "unset" && <TelemetryBanner />}
|
|
|
- {showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
|
|
-
|
|
|
- <RooHero />
|
|
|
- {taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
|
|
+ <div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-4">
|
|
|
+ {/* Moved Task Bar Header Here */}
|
|
|
+ {tasks.length !== 0 && (
|
|
|
+ <div className="flex text-vscode-descriptionForeground w-full mx-auto px-5 pt-3">
|
|
|
+ <div className="flex items-center gap-1 cursor-pointer" onClick={toggleExpanded}>
|
|
|
+ {tasks.length < 10 && (
|
|
|
+ <span className={`font-medium text-xs `}>{t("history:recentTasks")}</span>
|
|
|
+ )}
|
|
|
+ <span
|
|
|
+ className={`codicon ${isExpanded ? "codicon-eye" : "codicon-eye-closed"} scale-90`}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div
|
|
|
+ className={` w-full flex flex-col gap-4 m-auto ${isExpanded && tasks.length > 0 ? "mt-0" : ""} p-10 pt-5`}>
|
|
|
+ <RooHero />
|
|
|
+ {telemetrySetting === "unset" && <TelemetryBanner />}
|
|
|
+ {/* Show the task history preview if expanded and tasks exist */}
|
|
|
+ {taskHistory.length > 0 && isExpanded && <HistoryPreview />}
|
|
|
+ <p className="ext-vscode-editor-foreground leading-tight font-vscode text-center">
|
|
|
+ <Trans
|
|
|
+ i18nKey="chat:about"
|
|
|
+ components={{
|
|
|
+ DocsLink: (
|
|
|
+ <a
|
|
|
+ href="https://docs.roocode.com/getting-started/your-first-task"
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer">
|
|
|
+ the docs
|
|
|
+ </a>
|
|
|
+ ),
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ <RooTips cycle={false} />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
@@ -1270,28 +1297,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
// but becomes scrollable when the viewport is too small
|
|
|
*/}
|
|
|
{!task && (
|
|
|
- <AutoApproveMenu
|
|
|
- style={{
|
|
|
- marginBottom: -2,
|
|
|
- flex: "0 1 auto", // flex-grow: 0, flex-shrink: 1, flex-basis: auto
|
|
|
- minHeight: 0,
|
|
|
- }}
|
|
|
- />
|
|
|
+ <div className="mb-[-2px] flex-initial min-h-0">
|
|
|
+ <AutoApproveMenu />
|
|
|
+ </div>
|
|
|
)}
|
|
|
|
|
|
{task && (
|
|
|
<>
|
|
|
- <div style={{ flexGrow: 1, display: "flex" }} ref={scrollContainerRef}>
|
|
|
+ <div className="grow flex" ref={scrollContainerRef}>
|
|
|
<Virtuoso
|
|
|
ref={virtuosoRef}
|
|
|
key={task.ts} // trick to make sure virtuoso re-renders when task changes, and we use initialTopMostItemIndex to start at the bottom
|
|
|
- className="scrollable"
|
|
|
- style={{
|
|
|
- flexGrow: 1,
|
|
|
- overflowY: "scroll", // always show scrollbar
|
|
|
- }}
|
|
|
+ className="scrollable grow overflow-y-scroll"
|
|
|
components={{
|
|
|
- Footer: () => <div style={{ height: 5 }} />, // Add empty padding at the bottom
|
|
|
+ Footer: () => <div className="h-[5px]" />, // Add empty padding at the bottom
|
|
|
}}
|
|
|
// increasing top by 3_000 to prevent jumping around when user collapses a row
|
|
|
increaseViewportBy={{ top: 3_000, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
|
|
|
@@ -1310,40 +1329,33 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
</div>
|
|
|
<AutoApproveMenu />
|
|
|
{showScrollToBottom ? (
|
|
|
- <div
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- padding: "10px 15px 0px 15px",
|
|
|
- }}>
|
|
|
- <ScrollToBottomButton
|
|
|
+ <div className="flex px-[15px] pt-[10px]">
|
|
|
+ <div
|
|
|
+ className="bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_55%,_transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_90%,_transparent)] active:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_70%,_transparent)]"
|
|
|
onClick={() => {
|
|
|
scrollToBottomSmooth()
|
|
|
disableAutoScrollRef.current = false
|
|
|
}}
|
|
|
title={t("chat:scrollToBottom")}>
|
|
|
- <span className="codicon codicon-chevron-down" style={{ fontSize: "18px" }}></span>
|
|
|
- </ScrollToBottomButton>
|
|
|
+ <span className="codicon codicon-chevron-down text-[18px]"></span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
) : (
|
|
|
<div
|
|
|
- style={{
|
|
|
- opacity:
|
|
|
- primaryButtonText || secondaryButtonText || isStreaming
|
|
|
- ? enableButtons || (isStreaming && !didClickCancel)
|
|
|
- ? 1
|
|
|
- : 0.5
|
|
|
- : 0,
|
|
|
- display: "flex",
|
|
|
- padding: `${primaryButtonText || secondaryButtonText || isStreaming ? "10" : "0"}px 15px 0px 15px`,
|
|
|
- }}>
|
|
|
+ className={`flex ${
|
|
|
+ primaryButtonText || secondaryButtonText || isStreaming ? "px-[15px] pt-[10px]" : "p-0"
|
|
|
+ } ${
|
|
|
+ primaryButtonText || secondaryButtonText || isStreaming
|
|
|
+ ? enableButtons || (isStreaming && !didClickCancel)
|
|
|
+ ? "opacity-100"
|
|
|
+ : "opacity-50"
|
|
|
+ : "opacity-0"
|
|
|
+ }`}>
|
|
|
{primaryButtonText && !isStreaming && (
|
|
|
<VSCodeButton
|
|
|
appearance="primary"
|
|
|
disabled={!enableButtons}
|
|
|
- style={{
|
|
|
- flex: secondaryButtonText ? 1 : 2,
|
|
|
- marginRight: secondaryButtonText ? "6px" : "0",
|
|
|
- }}
|
|
|
+ className={secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0"}
|
|
|
title={
|
|
|
primaryButtonText === t("chat:retry.title")
|
|
|
? t("chat:retry.tooltip")
|
|
|
@@ -1372,10 +1384,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
<VSCodeButton
|
|
|
appearance="secondary"
|
|
|
disabled={!enableButtons && !(isStreaming && !didClickCancel)}
|
|
|
- style={{
|
|
|
- flex: isStreaming ? 2 : 1,
|
|
|
- marginLeft: isStreaming ? 0 : "6px",
|
|
|
- }}
|
|
|
+ className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
|
|
|
title={
|
|
|
isStreaming
|
|
|
? t("chat:cancel.tooltip")
|
|
|
@@ -1425,24 +1434,4 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
const ChatView = forwardRef(ChatViewComponent)
|
|
|
|
|
|
-const ScrollToBottomButton = styled.div`
|
|
|
- background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 55%, transparent);
|
|
|
- border-radius: 3px;
|
|
|
- overflow: hidden;
|
|
|
- cursor: pointer;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- flex: 1;
|
|
|
- height: 25px;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 90%, transparent);
|
|
|
- }
|
|
|
-
|
|
|
- &:active {
|
|
|
- background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 70%, transparent);
|
|
|
- }
|
|
|
-`
|
|
|
-
|
|
|
export default ChatView
|