mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Focus textarea when webview becomes visible; add getApiMetrics
This commit is contained in:
@@ -41,6 +41,17 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
// and executes code based on the message that is recieved
|
// and executes code based on the message that is recieved
|
||||||
this.setWebviewMessageListener(webviewView.webview)
|
this.setWebviewMessageListener(webviewView.webview)
|
||||||
|
|
||||||
|
// Listen for when the panel becomes visible
|
||||||
|
// https://github.com/microsoft/vscode-discussions/discussions/840
|
||||||
|
webviewView.onDidChangeVisibility((e: any) => {
|
||||||
|
if (e.visible) {
|
||||||
|
// Your view is visible
|
||||||
|
this.postMessageToWebview({ type: "action", action: "didBecomeVisible"})
|
||||||
|
} else {
|
||||||
|
// Your view is hidden
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// if the extension is starting a new session, clear previous task state
|
// if the extension is starting a new session, clear previous task state
|
||||||
this.resetTask()
|
this.resetTask()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
export interface ExtensionMessage {
|
export interface ExtensionMessage {
|
||||||
type: "action" | "state"
|
type: "action" | "state"
|
||||||
text?: string
|
text?: string
|
||||||
action?: "plusButtonTapped" | "settingsButtonTapped"
|
action?: "plusButtonTapped" | "settingsButtonTapped" | "didBecomeVisible"
|
||||||
state?: { didOpenOnce: boolean, apiKey?: string, maxRequestsPerTask?: number, claudeMessages: ClaudeMessage[] }
|
state?: { didOpenOnce: boolean, apiKey?: string, maxRequestsPerTask?: number, claudeMessages: ClaudeMessage[] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||||
window.addEventListener("message", (e: MessageEvent) => {
|
|
||||||
|
const handleMessage = (e: MessageEvent) => {
|
||||||
const message: ExtensionMessage = e.data
|
const message: ExtensionMessage = e.data
|
||||||
// switch message.type
|
// switch message.type
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
@@ -50,7 +51,13 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("message", handleMessage)
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// dummy data for messages
|
// dummy data for messages
|
||||||
@@ -136,6 +143,78 @@ const App: React.FC = () => {
|
|||||||
ts: generateRandomTimestamp(baseDate, 29),
|
ts: generateRandomTimestamp(baseDate, 29),
|
||||||
},
|
},
|
||||||
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 30) },
|
{ type: "say", say: "text", text: "Final message", ts: generateRandomTimestamp(baseDate, 30) },
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "text",
|
||||||
|
text: "Starting API requests",
|
||||||
|
ts: Date.now(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_started",
|
||||||
|
text: JSON.stringify({
|
||||||
|
request: "GET /api/data1",
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_finished",
|
||||||
|
text: JSON.stringify({
|
||||||
|
tokensIn: 10,
|
||||||
|
tokensOut: 20,
|
||||||
|
cost: 0.002,
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "text",
|
||||||
|
text: "Processing data...",
|
||||||
|
ts: Date.now() + 3000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_started",
|
||||||
|
text: JSON.stringify({
|
||||||
|
request: "POST /api/update1",
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 4000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_finished",
|
||||||
|
text: JSON.stringify({
|
||||||
|
tokensIn: 15,
|
||||||
|
tokensOut: 25,
|
||||||
|
cost: 0.003,
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "text",
|
||||||
|
text: "More processing...",
|
||||||
|
ts: Date.now() + 6000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_started",
|
||||||
|
text: JSON.stringify({
|
||||||
|
request: "GET /api/data2",
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 7000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "say",
|
||||||
|
say: "api_req_finished",
|
||||||
|
text: JSON.stringify({
|
||||||
|
tokensIn: 5,
|
||||||
|
tokensOut: 15,
|
||||||
|
cost: 0.001,
|
||||||
|
}),
|
||||||
|
ts: Date.now() + 8000,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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"
|
import TaskHeader from "./TaskHeader"
|
||||||
|
import { getApiMetrics } from "../utilities/getApiMetrics"
|
||||||
|
|
||||||
interface ChatViewProps {
|
interface ChatViewProps {
|
||||||
messages: ClaudeMessage[]
|
messages: ClaudeMessage[]
|
||||||
@@ -16,6 +17,8 @@ interface ChatViewProps {
|
|||||||
const ChatView = ({ messages }: ChatViewProps) => {
|
const ChatView = ({ messages }: ChatViewProps) => {
|
||||||
const task = messages.shift()
|
const task = messages.shift()
|
||||||
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [messages])
|
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages)), [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 [inputValue, setInputValue] = useState("")
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -110,7 +113,30 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textAreaRef.current && !textAreaHeight) {
|
if (textAreaRef.current && !textAreaHeight) {
|
||||||
setTextAreaHeight(textAreaRef.current.offsetHeight)
|
setTextAreaHeight(textAreaRef.current.offsetHeight)
|
||||||
textAreaRef.current.focus()
|
//textAreaRef.current.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMessage = (e: MessageEvent) => {
|
||||||
|
const message: ExtensionMessage = e.data
|
||||||
|
switch (message.type) {
|
||||||
|
case "action":
|
||||||
|
switch (message.action!) {
|
||||||
|
case "didBecomeVisible":
|
||||||
|
textAreaRef.current?.focus()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage)
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
textAreaRef.current?.focus()
|
||||||
|
}, 20)
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
window.removeEventListener("message", handleMessage)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
@@ -129,9 +155,9 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
|||||||
}}>
|
}}>
|
||||||
<TaskHeader
|
<TaskHeader
|
||||||
taskText={task?.text || ""}
|
taskText={task?.text || ""}
|
||||||
tokensIn={1000}
|
tokensIn={apiMetrics.totalTokensIn}
|
||||||
tokensOut={1500}
|
tokensOut={apiMetrics.totalTokensOut}
|
||||||
totalCost={0.0025}
|
totalCost={apiMetrics.totalCost}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="scrollable"
|
className="scrollable"
|
||||||
@@ -177,6 +203,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
|||||||
onHeightChange={() => scrollToBottom(true)}
|
onHeightChange={() => scrollToBottom(true)}
|
||||||
placeholder="Type a message..."
|
placeholder="Type a message..."
|
||||||
maxRows={10}
|
maxRows={10}
|
||||||
|
autoFocus={true}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
|
|||||||
55
webview-ui/src/utilities/getApiMetrics.ts
Normal file
55
webview-ui/src/utilities/getApiMetrics.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ClaudeMessage } from "@shared/ExtensionMessage"
|
||||||
|
|
||||||
|
interface ApiMetrics {
|
||||||
|
totalTokensIn: number
|
||||||
|
totalTokensOut: number
|
||||||
|
totalCost: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates API metrics from an array of ClaudeMessages.
|
||||||
|
*
|
||||||
|
* This function processes 'api_req_started' messages that have been combined with their
|
||||||
|
* corresponding 'api_req_finished' messages by the combineApiRequests function.
|
||||||
|
* It extracts and sums up the tokensIn, tokensOut, and cost from these messages.
|
||||||
|
*
|
||||||
|
* @param messages - An array of ClaudeMessage objects to process.
|
||||||
|
* @returns An ApiMetrics object containing totalTokensIn, totalTokensOut, and totalCost.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const messages = [
|
||||||
|
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data","tokensIn":10,"tokensOut":20,"cost":0.005}', ts: 1000 }
|
||||||
|
* ];
|
||||||
|
* const { totalTokensIn, totalTokensOut, totalCost } = getApiMetrics(messages);
|
||||||
|
* // Result: { totalTokensIn: 10, totalTokensOut: 20, totalCost: 0.005 }
|
||||||
|
*/
|
||||||
|
export function getApiMetrics(messages: ClaudeMessage[]): ApiMetrics {
|
||||||
|
const result: ApiMetrics = {
|
||||||
|
totalTokensIn: 0,
|
||||||
|
totalTokensOut: 0,
|
||||||
|
totalCost: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.forEach((message) => {
|
||||||
|
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(message.text)
|
||||||
|
const { tokensIn, tokensOut, cost } = parsedData
|
||||||
|
|
||||||
|
if (typeof tokensIn === "number") {
|
||||||
|
result.totalTokensIn += tokensIn
|
||||||
|
}
|
||||||
|
if (typeof tokensOut === "number") {
|
||||||
|
result.totalTokensOut += tokensOut
|
||||||
|
}
|
||||||
|
if (typeof cost === "number") {
|
||||||
|
result.totalCost += cost
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing JSON:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user