mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Use vscode scrollbar style; add TaskHeader
This commit is contained in:
@@ -4,11 +4,11 @@ import { VSCodeButton, VSCodeProgressRing, VSCodeTag } from "@vscode/webview-ui-
|
||||
|
||||
interface ChatRowProps {
|
||||
message: ClaudeMessage
|
||||
cost?: string
|
||||
}
|
||||
|
||||
const ChatRow: React.FC<ChatRowProps> = ({ message, cost }) => {
|
||||
const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const cost = message.text != null && message.say === "api_req_started" ? JSON.parse(message.text).cost : undefined
|
||||
|
||||
const getIconAndTitle = (type: ClaudeAsk | ClaudeSay | undefined): [JSX.Element | null, string | null] => {
|
||||
switch (type) {
|
||||
@@ -209,11 +209,6 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, cost }) => {
|
||||
<div
|
||||
style={{
|
||||
padding: "10px",
|
||||
borderBottom: "1px solid var(--vscode-panel-border)",
|
||||
backgroundColor:
|
||||
message.say === "task"
|
||||
? "var(--vscode-textBlockQuote-background)"
|
||||
: "var(--vscode-editor-background)",
|
||||
}}>
|
||||
{renderContent()}
|
||||
{isExpanded && message.say === "api_req_started" && (
|
||||
|
||||
@@ -7,82 +7,15 @@ import { ClaudeAskResponse } from "@shared/WebviewMessage"
|
||||
import ChatRow from "./ChatRow"
|
||||
import { combineCommandSequences } from "../utilities/combineCommandSequences"
|
||||
import { combineApiRequests } from "../utilities/combineApiRequests"
|
||||
import TaskHeader from "./TaskHeader"
|
||||
|
||||
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 = ({}: ChatViewProps) => {
|
||||
// dummy data for messages
|
||||
const generateRandomTimestamp = (baseDate: Date, rangeInDays: number): number => {
|
||||
const rangeInMs = rangeInDays * 24 * 60 * 60 * 1000 // convert days to milliseconds
|
||||
const randomOffset = Math.floor(Math.random() * rangeInMs * 2) - rangeInMs // rangeInMs * 2 to have offset in both directions
|
||||
return baseDate.getTime() + randomOffset
|
||||
}
|
||||
|
||||
const baseDate = new Date("2024-07-08T00:00:00Z")
|
||||
|
||||
const messages: ClaudeMessage[] = [
|
||||
{ type: "say", say: "task", text: "Starting task", ts: generateRandomTimestamp(baseDate, 1) },
|
||||
{
|
||||
type: "ask",
|
||||
ask: "request_limit_reached",
|
||||
text: "Request limit reached",
|
||||
ts: generateRandomTimestamp(baseDate, 2),
|
||||
},
|
||||
{ type: "ask", ask: "followup", text: "Any additional questions?", ts: generateRandomTimestamp(baseDate, 3) },
|
||||
{ type: "say", say: "error", text: "An error occurred", ts: generateRandomTimestamp(baseDate, 4) },
|
||||
|
||||
{ type: "say", say: "text", text: "Some general text", ts: generateRandomTimestamp(baseDate, 7) },
|
||||
{ type: "say", say: "tool", text: "Using a tool", ts: generateRandomTimestamp(baseDate, 8) },
|
||||
|
||||
// First command sequence
|
||||
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 9) },
|
||||
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 10) },
|
||||
{ type: "say", say: "api_req_started", text: JSON.stringify({ request: "GET /api/data" }), ts: generateRandomTimestamp(baseDate, 5) },
|
||||
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 11) },
|
||||
{ type: "say", say: "command_output", text: "directory1", ts: generateRandomTimestamp(baseDate, 12) },
|
||||
|
||||
{ type: "say", say: "text", text: "Interrupting text", ts: generateRandomTimestamp(baseDate, 13) },
|
||||
{ type: "say", say: "api_req_finished", text: JSON.stringify({ cost: "GET /api/data" }), ts: generateRandomTimestamp(baseDate, 6) },
|
||||
// Second command sequence
|
||||
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 14) },
|
||||
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 15) },
|
||||
|
||||
{ type: "ask", ask: "completion_result", text: "Task completed", ts: generateRandomTimestamp(baseDate, 16) },
|
||||
|
||||
// Third command sequence (no output)
|
||||
{ type: "ask", ask: "command", text: "echo Hello", ts: generateRandomTimestamp(baseDate, 17) },
|
||||
|
||||
// Testing combineApiRequests
|
||||
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 18) },
|
||||
{ type: "ask", ask: "command", text: "ls -l", ts: generateRandomTimestamp(baseDate, 19) },
|
||||
{ type: "say", say: "command_output", text: "file1.txt", ts: generateRandomTimestamp(baseDate, 20) },
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({ request: "GET /api/data" }),
|
||||
ts: generateRandomTimestamp(baseDate, 23),
|
||||
},
|
||||
{ type: "say", say: "command_output", text: "file2.txt", ts: generateRandomTimestamp(baseDate, 24) },
|
||||
{ type: "say", say: "text", text: "Some random text", ts: generateRandomTimestamp(baseDate, 25) },
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_finished",
|
||||
text: JSON.stringify({ cost: 0.005 }),
|
||||
ts: generateRandomTimestamp(baseDate, 26),
|
||||
},
|
||||
{ type: "ask", ask: "command", text: "pwd", ts: generateRandomTimestamp(baseDate, 27) },
|
||||
{ type: "say", say: "command_output", text: "/home/user", ts: generateRandomTimestamp(baseDate, 28) },
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify({ request: "POST /api/update" }),
|
||||
ts: generateRandomTimestamp(baseDate, 29),
|
||||
},
|
||||
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 30) },
|
||||
]
|
||||
|
||||
const ChatView = ({ messages }: ChatViewProps) => {
|
||||
|
||||
const task = messages.shift()
|
||||
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
|
||||
|
||||
const [inputValue, setInputValue] = useState("")
|
||||
@@ -93,9 +26,9 @@ const ChatView = ({}: ChatViewProps) => {
|
||||
|
||||
const [claudeAsk, setClaudeAsk] = useState<ClaudeAsk | undefined>(undefined)
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const scrollToBottom = (instant: boolean = false) => {
|
||||
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" })
|
||||
(messagesEndRef.current as any)?.scrollIntoView({ behavior: instant ? "instant" : "smooth", block: "nearest", inline: "start" })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -160,52 +93,62 @@ const ChatView = ({}: ChatViewProps) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
overflow: "hidden",
|
||||
backgroundColor: "gray",
|
||||
}}>
|
||||
<div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}>
|
||||
{modifiedMessages.map((message) => (
|
||||
<ChatRow message={message} />
|
||||
<TaskHeader
|
||||
taskText="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
tokensIn={1000}
|
||||
tokensOut={1500}
|
||||
totalCost={0.0025}
|
||||
/>
|
||||
<div
|
||||
className="scrollable"
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
{modifiedMessages.map((message, index) => (
|
||||
<ChatRow key={index} message={message} />
|
||||
))}
|
||||
<div style={{ float: "left", clear: "both" }} ref={messagesEndRef} />
|
||||
</div>
|
||||
<div style={{ position: "relative", paddingTop: "16px", paddingBottom: "16px" }}>
|
||||
<div style={{ padding: "16px" }}>
|
||||
<DynamicTextArea
|
||||
ref={textAreaRef}
|
||||
value={inputValue}
|
||||
disabled={textAreaDisabled}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onHeightChange={() => scrollToBottom()}
|
||||
onHeightChange={() => scrollToBottom(true)}
|
||||
placeholder="Type a message..."
|
||||
maxRows={10}
|
||||
style={{
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
backgroundColor: "var(--vscode-input-background, #3c3c3c)",
|
||||
color: "var(--vscode-input-foreground, #cccccc)",
|
||||
border: "1px solid var(--vscode-input-border, #3c3c3c)",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: "2px",
|
||||
fontFamily:
|
||||
"var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif)",
|
||||
fontSize: "var(--vscode-editor-font-size, 13px)",
|
||||
lineHeight: "var(--vscode-editor-line-height, 1.5)",
|
||||
fontFamily: "var(--vscode-font-family)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
lineHeight: "var(--vscode-editor-line-height)",
|
||||
resize: "none",
|
||||
overflow: "hidden",
|
||||
paddingTop: "8px",
|
||||
paddingBottom: "8px",
|
||||
paddingLeft: "8px",
|
||||
paddingRight: "40px", // Make room for button
|
||||
padding: "8px 40px 8px 8px",
|
||||
}}
|
||||
/>
|
||||
{textAreaHeight && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
right: "18px",
|
||||
height: `${textAreaHeight}px`,
|
||||
bottom: "18px",
|
||||
display: "flex",
|
||||
|
||||
84
webview-ui/src/components/TaskHeader.tsx
Normal file
84
webview-ui/src/components/TaskHeader.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useState } from "react"
|
||||
import TextTruncate from "react-text-truncate"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
taskText: string
|
||||
tokensIn: number
|
||||
tokensOut: number
|
||||
totalCost: number
|
||||
}
|
||||
|
||||
const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut, totalCost }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const toggleExpand = () => setIsExpanded(!isExpanded)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "10px",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-badge-background)",
|
||||
color: "var(--vscode-badge-foreground)",
|
||||
borderRadius: "3px",
|
||||
padding: "8px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<div style={{ fontSize: "var(--vscode-font-size)", lineHeight: "1.5" }}>
|
||||
<TextTruncate
|
||||
line={isExpanded ? 0 : 3}
|
||||
element="span"
|
||||
truncateText="…"
|
||||
text={taskText}
|
||||
textTruncateChild={
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
onClick={toggleExpand}>
|
||||
See more
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{isExpanded && (
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
onClick={toggleExpand}>
|
||||
See less
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: "bold" }}>Tokens:</span>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i className="codicon codicon-arrow-down" style={{ fontSize: "12px", marginBottom: "-1px" }} />
|
||||
{tokensIn.toLocaleString()}
|
||||
</span>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<i className="codicon codicon-arrow-up" style={{ fontSize: "12px", marginBottom: "-1px" }} />
|
||||
{tokensOut.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: "bold" }}>API Cost:</span>
|
||||
<span>${totalCost.toFixed(4)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaskHeader
|
||||
Reference in New Issue
Block a user