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:
25
webview-ui/package-lock.json
generated
25
webview-ui/package-lock.json
generated
@@ -19,12 +19,14 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"react-text-truncate": "^0.19.0",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"rewire": "^7.0.0",
|
"rewire": "^7.0.0",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react-text-truncate": "^0.14.4",
|
||||||
"@types/vscode-webview": "^1.57.5"
|
"@types/vscode-webview": "^1.57.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4562,6 +4564,16 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-text-truncate": {
|
||||||
|
"version": "0.14.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.4.tgz",
|
||||||
|
"integrity": "sha512-qdw8522RqdYkTX0FShDPDx8hIRVjPydW8PXl/wKpPGpAtjJTsaNiFOe0fxMRLXIEQaAZvC5VLlKGGONAetb6nQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||||
@@ -16143,6 +16155,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-text-truncate": {
|
||||||
|
"version": "0.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-text-truncate/-/react-text-truncate-0.19.0.tgz",
|
||||||
|
"integrity": "sha512-QxHpZABfGG0Z3WEYbRTZ+rXdZn50Zvp+sWZXgVAd7FCKAMzv/kcwctTpNmWgXDTpAoHhMjOVwmgRtX3x5yeF4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.5.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^15.4.1 || ^16.0.0 || ^17.0.0 || || ^18.0.0",
|
||||||
|
"react-dom": "^15.4.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-textarea-autosize": {
|
"node_modules/react-textarea-autosize": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"react-text-truncate": "^0.19.0",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"rewire": "^7.0.0",
|
"rewire": "^7.0.0",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react-text-truncate": "^0.14.4",
|
||||||
"@types/vscode-webview": "^1.57.5"
|
"@types/vscode-webview": "^1.57.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,91 @@ const App: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// 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, this is my requeirements",
|
||||||
|
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) },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showWelcome ? (
|
{showWelcome ? (
|
||||||
@@ -66,7 +151,7 @@ const App: React.FC = () => {
|
|||||||
onDone={() => setShowSettings(false)}
|
onDone={() => setShowSettings(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ChatView messages={claudeMessages} />
|
<ChatView messages={messages} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { VSCodeButton, VSCodeProgressRing, VSCodeTag } from "@vscode/webview-ui-
|
|||||||
|
|
||||||
interface ChatRowProps {
|
interface ChatRowProps {
|
||||||
message: ClaudeMessage
|
message: ClaudeMessage
|
||||||
cost?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatRow: React.FC<ChatRowProps> = ({ message, cost }) => {
|
const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
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] => {
|
const getIconAndTitle = (type: ClaudeAsk | ClaudeSay | undefined): [JSX.Element | null, string | null] => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -209,11 +209,6 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, cost }) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "10px",
|
padding: "10px",
|
||||||
borderBottom: "1px solid var(--vscode-panel-border)",
|
|
||||||
backgroundColor:
|
|
||||||
message.say === "task"
|
|
||||||
? "var(--vscode-textBlockQuote-background)"
|
|
||||||
: "var(--vscode-editor-background)",
|
|
||||||
}}>
|
}}>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
{isExpanded && message.say === "api_req_started" && (
|
{isExpanded && message.say === "api_req_started" && (
|
||||||
|
|||||||
@@ -7,82 +7,15 @@ import { ClaudeAskResponse } from "@shared/WebviewMessage"
|
|||||||
import ChatRow from "./ChatRow"
|
import ChatRow from "./ChatRow"
|
||||||
import { combineCommandSequences } from "../utilities/combineCommandSequences"
|
import { combineCommandSequences } from "../utilities/combineCommandSequences"
|
||||||
import { combineApiRequests } from "../utilities/combineApiRequests"
|
import { combineApiRequests } from "../utilities/combineApiRequests"
|
||||||
|
import TaskHeader from "./TaskHeader"
|
||||||
|
|
||||||
interface ChatViewProps {
|
interface ChatViewProps {
|
||||||
messages: ClaudeMessage[]
|
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)
|
// 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) => {
|
const ChatView = ({ messages }: 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 task = messages.shift()
|
||||||
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
|
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState("")
|
const [inputValue, setInputValue] = useState("")
|
||||||
@@ -93,9 +26,9 @@ const ChatView = ({}: ChatViewProps) => {
|
|||||||
|
|
||||||
const [claudeAsk, setClaudeAsk] = useState<ClaudeAsk | undefined>(undefined)
|
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
|
// 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(() => {
|
useEffect(() => {
|
||||||
@@ -160,52 +93,62 @@ const ChatView = ({}: ChatViewProps) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100vh",
|
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
backgroundColor: "gray",
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}>
|
<TaskHeader
|
||||||
{modifiedMessages.map((message) => (
|
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."
|
||||||
<ChatRow message={message} />
|
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 style={{ float: "left", clear: "both" }} ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ position: "relative", paddingTop: "16px", paddingBottom: "16px" }}>
|
<div style={{ padding: "16px" }}>
|
||||||
<DynamicTextArea
|
<DynamicTextArea
|
||||||
ref={textAreaRef}
|
ref={textAreaRef}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
disabled={textAreaDisabled}
|
disabled={textAreaDisabled}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onHeightChange={() => scrollToBottom()}
|
onHeightChange={() => scrollToBottom(true)}
|
||||||
placeholder="Type a message..."
|
placeholder="Type a message..."
|
||||||
maxRows={10}
|
maxRows={10}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
backgroundColor: "var(--vscode-input-background, #3c3c3c)",
|
backgroundColor: "var(--vscode-input-background)",
|
||||||
color: "var(--vscode-input-foreground, #cccccc)",
|
color: "var(--vscode-input-foreground)",
|
||||||
border: "1px solid var(--vscode-input-border, #3c3c3c)",
|
border: "1px solid var(--vscode-input-border)",
|
||||||
borderRadius: "2px",
|
borderRadius: "2px",
|
||||||
fontFamily:
|
fontFamily: "var(--vscode-font-family)",
|
||||||
"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)",
|
||||||
fontSize: "var(--vscode-editor-font-size, 13px)",
|
lineHeight: "var(--vscode-editor-line-height)",
|
||||||
lineHeight: "var(--vscode-editor-line-height, 1.5)",
|
|
||||||
resize: "none",
|
resize: "none",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
paddingTop: "8px",
|
padding: "8px 40px 8px 8px",
|
||||||
paddingBottom: "8px",
|
|
||||||
paddingLeft: "8px",
|
|
||||||
paddingRight: "40px", // Make room for button
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{textAreaHeight && (
|
{textAreaHeight && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: "12px",
|
right: "18px",
|
||||||
height: `${textAreaHeight}px`,
|
height: `${textAreaHeight}px`,
|
||||||
bottom: "18px",
|
bottom: "18px",
|
||||||
display: "flex",
|
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
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
} */
|
} */
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
||||||
}
|
}
|
||||||
@@ -19,3 +17,62 @@ textarea:focus {
|
|||||||
vscode-button::part(control):focus {
|
vscode-button::part(control):focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use vscode native scrollbar styles
|
||||||
|
https://github.com/gitkraken/vscode-gitlens/blob/b1d71d4844523e8b2ef16f9e007068e91f46fd88/src/webviews/apps/home/home.scss
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.scrollable,
|
||||||
|
.scrollable {
|
||||||
|
border-color: transparent;
|
||||||
|
transition: border-color 0.7s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:hover.scrollable,
|
||||||
|
body:hover .scrollable,
|
||||||
|
body:focus-within.scrollable,
|
||||||
|
body:focus-within .scrollable {
|
||||||
|
border-color: var(--vscode-scrollbarSlider-background);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-corner {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: inherit;
|
||||||
|
border-right-style: inset;
|
||||||
|
border-right-width: calc(100vw + 100vh);
|
||||||
|
border-radius: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
border-color: var(--vscode-scrollbarSlider-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:active {
|
||||||
|
border-color: var(--vscode-scrollbarSlider-activeBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fix VSCode ignoring webkit scrollbar modifiers
|
||||||
|
https://github.com/microsoft/vscode/issues/213045
|
||||||
|
*/
|
||||||
|
@supports selector(::-webkit-scrollbar) {
|
||||||
|
html {
|
||||||
|
scrollbar-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user