Fixed garbage collection of aborted tasks; handle if run from root directory then don’t read/write; fixed scroll to bottom; fix other small bugs

This commit is contained in:
Saoud Rizwan
2024-07-09 23:44:20 -04:00
parent 9867a6a597
commit 7170d2a2e8
8 changed files with 101 additions and 26 deletions

View File

@@ -6,7 +6,7 @@ import SettingsView from "./components/SettingsView"
import { ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage"
import WelcomeView from "./components/WelcomeView"
import { vscode } from "./utilities/vscode"
import { mockMessages } from "./utilities/mockMessages"
//import { mockMessages } from "./utilities/mockMessages"
/*
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
@@ -74,7 +74,7 @@ const App: React.FC = () => {
onDone={() => setShowSettings(false)}
/>
) : (
<ChatView messages={mockMessages} />
<ChatView messages={claudeMessages} />
)}
</>
)

View File

@@ -256,7 +256,6 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
<CodeBlock
code={output}
language="shell-session"
path="src/components/WelcomeView.tsx/src/components/WelcomeView.tsx"
/>
</>
)}
@@ -295,19 +294,24 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
}
}
// we need to return null here instead of in getContent since that way would result in padding being applied
if (message.say === "api_req_finished") {
return null // Don't render anything for this message type
}
if (message.type === "ask" && message.ask === "completion_result" && message.text === "") {
return null // Don't render anything for this message type
}
return (
<div
style={{
padding: "10px 5px 10px 20px",
padding: "10px 20px 10px 20px",
}}>
{renderContent()}
{isExpanded && message.say === "api_req_started" && (
<div style={{ marginTop: "10px" }}>
<CodeBlock code={JSON.stringify(JSON.parse(message.text || "{}").request)} language="json" />
<CodeBlock code={JSON.stringify(JSON.parse(message.text || "{}").request, null, 2)} language="json" />
</div>
)}
</div>

View File

@@ -9,19 +9,20 @@ import { combineCommandSequences } from "../utilities/combineCommandSequences"
import { combineApiRequests } from "../utilities/combineApiRequests"
import TaskHeader from "./TaskHeader"
import { getApiMetrics } from "../utilities/getApiMetrics"
import { animateScroll as scroll } from "react-scroll"
interface ChatViewProps {
messages: ClaudeMessage[]
}
// maybe instead of storing state in App, just make chatview always show so dont conditionally load/unload? need to make sure messages are persisted (i remember seeing something about how webviews can be frozen in docs)
const ChatView = ({ messages }: ChatViewProps) => {
const task = messages.shift()
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined
const task = messages.length > 0 ? messages[0] : undefined // 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 ClaudeDev.abort)
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
// has to be after api_req_finished are all reduced into api_req_started messages
const apiMetrics = useMemo(() => getApiMetrics(modifiedMessages), [modifiedMessages])
const [inputValue, setInputValue] = useState("")
const messagesEndRef = useRef<HTMLDivElement>(null)
const textAreaRef = useRef<HTMLTextAreaElement>(null)
const [textAreaHeight, setTextAreaHeight] = useState<number | undefined>(undefined)
const [textAreaDisabled, setTextAreaDisabled] = useState(false)
@@ -33,12 +34,12 @@ const ChatView = ({ messages }: ChatViewProps) => {
const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
const scrollToBottom = (instant: boolean = false) => {
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
;(messagesEndRef.current as any)?.scrollIntoView({
behavior: instant ? "instant" : "smooth",
block: "nearest",
inline: "start",
})
const options = {
containerId: "chat-view-container",
duration: instant ? 0 : 500,
smooth: !instant,
}
scroll.scrollToBottom(options)
}
// scroll to bottom when new message is added
@@ -50,8 +51,13 @@ const ChatView = ({ messages }: ChatViewProps) => {
[modifiedMessages]
)
useEffect(() => {
scrollToBottom()
}, [visibleMessages.length])
const timer = setTimeout(() => {
scrollToBottom()
}, 0)
return () => {
clearTimeout(timer)
}
}, [visibleMessages])
useEffect(() => {
// if last message is an ask, show user ask UI
@@ -114,13 +120,23 @@ const ChatView = ({ messages }: ChatViewProps) => {
break
}
} else {
// this would get called after sending the first message, so we have to watch messages.length instead
// No messages, so user has to submit a task
// setTextAreaDisabled(false)
// setClaudeAsk(undefined)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
}
}, [messages])
useEffect(() => {
if (messages.length === 0) {
setTextAreaDisabled(false)
setClaudeAsk(undefined)
setPrimaryButtonText(undefined)
setSecondaryButtonText(undefined)
}
}, [messages])
}, [messages.length])
const handleSendMessage = () => {
const text = inputValue.trim()
@@ -249,6 +265,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
/>
)}
<div
id="chat-view-container"
className="scrollable"
style={{
flexGrow: 1,
@@ -257,7 +274,6 @@ const ChatView = ({ messages }: ChatViewProps) => {
{modifiedMessages.map((message, index) => (
<ChatRow key={index} message={message} />
))}
<div style={{ float: "left", clear: "both" }} ref={messagesEndRef} />
</div>
{(primaryButtonText || secondaryButtonText) && (
<div style={{ display: "flex", padding: "10px 15px 0px 15px" }}>
@@ -282,7 +298,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
)}
</div>
)}
<div style={{ padding: "10px 15px" }}>
<div style={{ padding: "10px 15px", opacity: textAreaDisabled ? 0.7 : 1 }}>
<DynamicTextArea
ref={textAreaRef}
value={inputValue}
@@ -305,20 +321,24 @@ const ChatView = ({ messages }: ChatViewProps) => {
lineHeight: "var(--vscode-editor-line-height)",
resize: "none",
overflow: "hidden",
padding: "8px 40px 8px 8px",
padding: "8px 36px 8px 8px",
}}
/>
{textAreaHeight && (
<div
style={{
position: "absolute",
right: "18px",
right: "20px",
height: `${textAreaHeight}px`,
bottom: "12px",
display: "flex",
alignItems: "center",
}}>
<VSCodeButton disabled={textAreaDisabled} appearance="icon" aria-label="Send Message" onClick={handleSendMessage}>
<VSCodeButton
disabled={textAreaDisabled}
appearance="icon"
aria-label="Send Message"
onClick={handleSendMessage}>
<span className="codicon codicon-send"></span>
</VSCodeButton>
</div>

View File

@@ -89,7 +89,6 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
<div
style={{
borderRadius: "3px",
marginRight: "2px",
backgroundColor: backgroundColor,
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
}}>