From e322c3a6dbb97d9713c3a33c2453a9c5417cee04 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 12 Mar 2025 14:07:02 -0400 Subject: [PATCH 1/2] Fixed --- src/shared/api.ts | 2 +- webview-ui/src/components/chat/ChatView.tsx | 31 +++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index 6c381b09447..c217f9121dc 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -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, diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 9afb4b2524d..09a1a25b3b6 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -14,6 +14,7 @@ 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" @@ -463,9 +464,35 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie startNewTask() }, [startNewTask]) + const [pearAiModels, setPearAiModels] = useState>({ + [pearAiDefaultModelId]: pearAiDefaultModelInfo, + }) + + // Fetch PearAI models when provider is selected + 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]) + const { selectedModelInfo } = useMemo(() => { - return normalizeApiConfiguration(apiConfiguration) - }, [apiConfiguration]) + return normalizeApiConfiguration(apiConfiguration, pearAiModels) + }, [apiConfiguration, pearAiModels]) const selectImages = useCallback(() => { vscode.postMessage({ type: "selectImages" }) From 7c38e001fa659dbafdfbc0868e411c94c75c4004 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 12 Mar 2025 14:12:10 -0400 Subject: [PATCH 2/2] Working --- webview-ui/src/components/chat/ChatView.tsx | 27 ++-------------- webview-ui/src/components/chat/TaskHeader.tsx | 11 +++++-- .../src/components/settings/ApiOptions.tsx | 27 ++-------------- .../src/components/settings/ModelPicker.tsx | 7 ++-- webview-ui/src/hooks/usePearAiModels.ts | 32 +++++++++++++++++++ 5 files changed, 49 insertions(+), 55 deletions(-) create mode 100644 webview-ui/src/hooks/usePearAiModels.ts diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 09a1a25b3b6..cd2773d0808 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -21,6 +21,7 @@ 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" @@ -464,31 +465,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie startNewTask() }, [startNewTask]) - const [pearAiModels, setPearAiModels] = useState>({ - [pearAiDefaultModelId]: pearAiDefaultModelInfo, - }) - - // Fetch PearAI models when provider is selected - 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]) + const pearAiModels = usePearAiModels(apiConfiguration) const { selectedModelInfo } = useMemo(() => { return normalizeApiConfiguration(apiConfiguration, pearAiModels) diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 520c601cb8d..329ce081eed 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -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" @@ -42,7 +43,11 @@ const TaskHeader: React.FC = ({ 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) @@ -51,7 +56,7 @@ const TaskHeader: React.FC = ({ 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 @@ -59,7 +64,7 @@ const TaskHeader: React.FC = ({ - 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) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 2496f09a41c..854af7ecf5e 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -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" @@ -92,9 +93,7 @@ const ApiOptions = ({ }) const [openAiModels, setOpenAiModels] = useState | null>(null) - const [pearAiModels, setPearAiModels] = useState>({ - [pearAiDefaultModelId]: pearAiDefaultModelInfo, - }) + const pearAiModels = usePearAiModels(apiConfiguration) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) @@ -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) || diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index 5a7737edd56..f6c2c01c3fc 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -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" @@ -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( diff --git a/webview-ui/src/hooks/usePearAiModels.ts b/webview-ui/src/hooks/usePearAiModels.ts new file mode 100644 index 00000000000..1cc2f4906bf --- /dev/null +++ b/webview-ui/src/hooks/usePearAiModels.ts @@ -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>({ + [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 +}