Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ export const pearAiModels = {
contextWindow: 64000,
// Default values for required fields, but actual values will be inherited from underlying model
supportsPromptCache: true,
supportsImages: true,
supportsImages: false,
supportsComputerUse: false,
// Base pricing
inputPrice: 0.014,
Expand Down
8 changes: 6 additions & 2 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import {
import { McpServer, McpTool } from "../../../../src/shared/mcp"
import { findLast } from "../../../../src/shared/array"
import { combineApiRequests } from "../../../../src/shared/combineApiRequests"
import { ModelInfo, pearAiDefaultModelId, pearAiDefaultModelInfo, PEARAI_URL } from "../../../../src/shared/api"
import { combineCommandSequences } from "../../../../src/shared/combineCommandSequences"
import { getApiMetrics } from "../../../../src/shared/getApiMetrics"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import HistoryPreview from "../history/HistoryPreview"
import { normalizeApiConfiguration } from "../settings/ApiOptions"
import { usePearAiModels } from "../../hooks/usePearAiModels"
import Announcement from "./Announcement"
import BrowserSessionRow from "./BrowserSessionRow"
import ChatRow from "./ChatRow"
Expand Down Expand Up @@ -463,9 +465,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
startNewTask()
}, [startNewTask])

const pearAiModels = usePearAiModels(apiConfiguration)

const { selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
return normalizeApiConfiguration(apiConfiguration, pearAiModels)
}, [apiConfiguration, pearAiModels])

const selectImages = useCallback(() => {
vscode.postMessage({ type: "selectImages" })
Expand Down
11 changes: 8 additions & 3 deletions webview-ui/src/components/chat/TaskHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatLargeNumber } from "../../utils/format"
import { normalizeApiConfiguration } from "../settings/ApiOptions"
import { Button } from "../ui"
import { HistoryItem } from "../../../../src/shared/HistoryItem"
import { usePearAiModels } from "../../hooks/usePearAiModels"
import { BackspaceIcon, ChatBubbleOvalLeftIcon } from "@heroicons/react/24/outline"
import { vscBadgeBackground, vscEditorBackground, vscInputBackground } from "../ui"
import { DownloadIcon } from "@radix-ui/react-icons"
Expand Down Expand Up @@ -42,7 +43,11 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
onClose,
}) => {
const { apiConfiguration, currentTaskItem } = useExtensionState()
const { selectedModelInfo } = useMemo(() => normalizeApiConfiguration(apiConfiguration), [apiConfiguration])
const pearAiModels = usePearAiModels(apiConfiguration)
const { selectedModelInfo } = useMemo(
() => normalizeApiConfiguration(apiConfiguration, pearAiModels),
[apiConfiguration, pearAiModels],
)
const [isTaskExpanded, setIsTaskExpanded] = useState(true)
const [isTextExpanded, setIsTextExpanded] = useState(false)
const [showSeeMore, setShowSeeMore] = useState(false)
Expand All @@ -51,15 +56,15 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
const contextWindow = selectedModelInfo?.contextWindow || 1

/*
When dealing with event listeners in React components that depend on state variables, we face a challenge. We want our listener to always use the most up-to-date version of a callback function that relies on current state, but we don't want to constantly add and remove event listeners as that function updates. This scenario often arises with resize listeners or other window events. Simply adding the listener in a useEffect with an empty dependency array risks using stale state, while including the callback in the dependencies can lead to unnecessary re-registrations of the listener. There are react hook libraries that provide a elegant solution to this problem by utilizing the useRef hook to maintain a reference to the latest callback function without triggering re-renders or effect re-runs. This approach ensures that our event listener always has access to the most current state while minimizing performance overhead and potential memory leaks from multiple listener registrations.
When dealing with event listeners in React components that depend on state variables, we face a challenge. We want our listener to always use the most up-to-date version of a callback function that relies on current state, but we don't want to constantly add and remove event listeners as that function updates. This scenario often arises with resize listeners or other window events. Simply adding the listener in a useEffect with an empty dependency array risks using stale state, while including the callback in the dependencies can lead to unnecessary re-registrations of the listener. There are react hook libraries that provide a elegant solution to this problem by utilizing the useRef hook to maintain a reference to the latest callback function without triggering re-renders or effect re-runs. This approach ensures that our event listener always has access to the most current state while minimizing performance overhead and potential memory leaks from multiple listener registrations.
Sources
- https://usehooks-ts.com/react-hook/use-event-listener
- https://streamich.github.io/react-use/?path=/story/sensors-useevent--docs
- https://github.com/streamich/react-use/blob/master/src/useEvent.ts
- https://stackoverflow.com/questions/55565444/how-to-register-event-with-useeffect-hooks

Before:

const updateMaxHeight = useCallback(() => {
if (isExpanded && textContainerRef.current) {
const maxHeight = window.innerHeight * (3 / 5)
Expand Down
27 changes: 2 additions & 25 deletions webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { vscode } from "../../utils/vscode"
import VSCodeButtonLink from "../common/VSCodeButtonLink"
import { ModelInfoView } from "./ModelInfoView"
import { usePearAiModels } from "../../hooks/usePearAiModels"
import { DROPDOWN_Z_INDEX } from "./styles"
import { ModelPicker } from "./ModelPicker"
import { validateApiConfiguration, validateModelId } from "@/utils/validate"
Expand Down Expand Up @@ -92,9 +93,7 @@ const ApiOptions = ({
})

const [openAiModels, setOpenAiModels] = useState<Record<string, ModelInfo> | null>(null)
const [pearAiModels, setPearAiModels] = useState<Record<string, ModelInfo>>({
[pearAiDefaultModelId]: pearAiDefaultModelInfo,
})
const pearAiModels = usePearAiModels(apiConfiguration)

const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
Expand Down Expand Up @@ -167,28 +166,6 @@ const ApiOptions = ({
],
)

// Fetch PearAI models when provider is selected
useEffect(() => {
if (selectedProvider === "pearai") {
const fetchPearAiModels = async () => {
try {
const res = await fetch(`${PEARAI_URL}/getPearAIAgentModels`)
if (!res.ok) throw new Error("Failed to fetch models")
const config = await res.json()

if (config.models && Object.keys(config.models).length > 0) {
console.log("Models successfully loaded from server")
setPearAiModels(config.models)
}
} catch (error) {
console.error("Error fetching PearAI models:", error)
}
}

fetchPearAiModels()
}
}, [selectedProvider, setPearAiModels])

useEffect(() => {
const apiValidationResult =
validateApiConfiguration(apiConfiguration) ||
Expand Down
7 changes: 5 additions & 2 deletions webview-ui/src/components/settings/ModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem }
import { ApiConfiguration, ModelInfo } from "../../../../src/shared/api"

import { normalizeApiConfiguration } from "./ApiOptions"
import { usePearAiModels } from "../../hooks/usePearAiModels"
import { ThinkingBudget } from "./ThinkingBudget"
import { ModelInfoView } from "./ModelInfoView"

Expand Down Expand Up @@ -45,9 +46,11 @@ export const ModelPicker = ({

const modelIds = useMemo(() => Object.keys(models ?? {}).sort((a, b) => a.localeCompare(b)), [models])

const pearAiModels = usePearAiModels(apiConfiguration)

const { selectedModelId, selectedModelInfo } = useMemo(
() => normalizeApiConfiguration(apiConfiguration),
[apiConfiguration],
() => normalizeApiConfiguration(apiConfiguration, pearAiModels),
[apiConfiguration, pearAiModels],
)

const onSelect = useCallback(
Expand Down
32 changes: 32 additions & 0 deletions webview-ui/src/hooks/usePearAiModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState, useEffect } from "react"
import { ModelInfo, pearAiDefaultModelId, pearAiDefaultModelInfo, PEARAI_URL } from "../../../src/shared/api"
import type { ApiConfiguration } from "../../../src/shared/api"

export const usePearAiModels = (apiConfiguration?: ApiConfiguration) => {
const [pearAiModels, setPearAiModels] = useState<Record<string, ModelInfo>>({
[pearAiDefaultModelId]: pearAiDefaultModelInfo,
})

useEffect(() => {
if (apiConfiguration?.apiProvider === "pearai") {
const fetchPearAiModels = async () => {
try {
const res = await fetch(`${PEARAI_URL}/getPearAIAgentModels`)
if (!res.ok) throw new Error("Failed to fetch models")
const config = await res.json()

if (config.models && Object.keys(config.models).length > 0) {
console.log("Models successfully loaded from server")
setPearAiModels(config.models)
}
} catch (error) {
console.error("Error fetching PearAI models:", error)
}
}

fetchPearAiModels()
}
}, [apiConfiguration?.apiProvider])

return pearAiModels
}
Loading