diff --git a/client/src/App.tsx b/client/src/App.tsx index 59d15ba06..ceae9dae7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -151,6 +151,9 @@ const App = () => { const [resourceContentMap, setResourceContentMap] = useState< Record >({}); + const [resourceErrorMap, setResourceErrorMap] = useState< + Record + >({}); const [fetchingResources, setFetchingResources] = useState>( new Set(), ); @@ -902,13 +905,32 @@ const App = () => { setPromptContent(JSON.stringify(response, null, 2)); }; - const readResource = async (uri: string) => { - if (fetchingResources.has(uri) || resourceContentMap[uri]) { + const readResource = async ( + uri: string, + { bypassCache = false }: { bypassCache?: boolean } = {}, + ) => { + if (fetchingResources.has(uri)) { + return; + } + + const hasOwn = Object.prototype.hasOwnProperty; + if ( + !bypassCache && + hasOwn.call(resourceContentMap, uri) && + !hasOwn.call(resourceErrorMap, uri) + ) { + setResourceContent(resourceContentMap[uri]); return; } console.log("[App] Reading resource:", uri); setFetchingResources((prev) => new Set(prev).add(uri)); + setResourceErrorMap((prev) => { + if (!hasOwn.call(prev, uri)) return prev; + const next = { ...prev }; + delete next[uri]; + return next; + }); lastToolCallOriginTabRef.current = currentTabRef.current; try { @@ -934,9 +956,9 @@ const App = () => { } catch (error) { console.error(`[App] Failed to read resource ${uri}:`, error); const errorString = (error as Error).message ?? String(error); - setResourceContentMap((prev) => ({ + setResourceErrorMap((prev) => ({ ...prev, - [uri]: JSON.stringify({ error: errorString }), + [uri]: errorString, })); } finally { setFetchingResources((prev) => { @@ -1482,9 +1504,9 @@ const App = () => { setResourceTemplates([]); setNextResourceTemplateCursor(undefined); }} - readResource={(uri) => { + readResource={(uri, options) => { clearError("resources"); - readResource(uri); + readResource(uri, options); }} selectedResource={selectedResource} setSelectedResource={(resource) => { @@ -1590,6 +1612,7 @@ const App = () => { nextCursor={nextToolCursor} error={errors.tools} resourceContent={resourceContentMap} + resourceError={resourceErrorMap} onReadResource={(uri: string) => { clearError("resources"); readResource(uri); diff --git a/client/src/components/ResourceLinkView.tsx b/client/src/components/ResourceLinkView.tsx index 9a42747b1..c01cf6728 100644 --- a/client/src/components/ResourceLinkView.tsx +++ b/client/src/components/ResourceLinkView.tsx @@ -7,6 +7,7 @@ interface ResourceLinkViewProps { description?: string; mimeType?: string; resourceContent: string; + resourceError?: string; onReadResource?: (uri: string) => void; } @@ -17,6 +18,7 @@ const ResourceLinkView = memo( description, mimeType, resourceContent, + resourceError, onReadResource, }: ResourceLinkViewProps) => { const [{ expanded, loading }, setState] = useState({ @@ -24,18 +26,32 @@ const ResourceLinkView = memo( loading: false, }); - const expandedContent = useMemo( - () => - expanded && resourceContent ? ( + const expandedContent = useMemo(() => { + if (!expanded) return null; + if (resourceError !== undefined) { + return ( +
+
+ Error: +
+
+ {resourceError} +
+
+ ); + } + if (resourceContent) { + return (
Resource:
- ) : null, - [expanded, resourceContent], - ); + ); + } + return null; + }, [expanded, resourceContent, resourceError]); const handleClick = useCallback(() => { if (!onReadResource) return; diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 36e5cec8f..815d6a596 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -46,7 +46,7 @@ const ResourcesTab = ({ clearResources: () => void; listResourceTemplates: () => void; clearResourceTemplates: () => void; - readResource: (uri: string) => void; + readResource: (uri: string, opts?: { bypassCache?: boolean }) => void; selectedResource: Resource | null; setSelectedResource: (resource: Resource | null) => void; handleCompletion: ( @@ -229,7 +229,9 @@ const ResourcesTab = ({