mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
fixes Python: DevUI fails when uploading Pdf file (tested on Python Foundry Agent) (#2675)
Fixes #2652
This commit is contained in:
committed by
GitHub
Unverified
parent
551219cb55
commit
0675000f4b
@@ -81,6 +81,7 @@ export default function App() {
|
||||
|
||||
// Toast state and actions
|
||||
const toasts = useDevUIStore((state) => state.toasts);
|
||||
const addToast = useDevUIStore((state) => state.addToast);
|
||||
const removeToast = useDevUIStore((state) => state.removeToast);
|
||||
|
||||
// Initialize app - load agents and workflows
|
||||
@@ -174,6 +175,12 @@ export default function App() {
|
||||
`Failed to load full info for first entity ${selectedEntity.id}:`,
|
||||
error
|
||||
);
|
||||
// Show toast for entity load errors (don't use setEntityError - that kills the whole UI)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
addToast({
|
||||
type: "error",
|
||||
message: `Failed to load "${selectedEntity.id}": ${errorMessage}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +201,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [setAgents, setWorkflows, selectEntity, updateAgent, updateWorkflow, setIsLoadingEntities, setEntityError, setShowEntityNotFoundToast]);
|
||||
}, [setAgents, setWorkflows, selectEntity, updateAgent, updateWorkflow, setIsLoadingEntities, setEntityError, setShowEntityNotFoundToast, addToast, setEntities]);
|
||||
|
||||
// Handle auth token submission
|
||||
const handleAuthTokenSubmit = useCallback(async () => {
|
||||
@@ -284,10 +291,16 @@ export default function App() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load full info for ${item.id}:`, error);
|
||||
// Show toast for entity load errors (don't use setEntityError - that kills the whole UI)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
addToast({
|
||||
type: "error",
|
||||
message: `Failed to load "${item.id}": ${errorMessage}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectEntity, updateAgent, updateWorkflow]
|
||||
[selectEntity, updateAgent, updateWorkflow, addToast]
|
||||
);
|
||||
|
||||
// Handle debug events from active view
|
||||
|
||||
+115
-22
@@ -3,7 +3,7 @@
|
||||
* This is the CORRECT implementation that works with OpenAI types only
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Download,
|
||||
FileText,
|
||||
@@ -80,8 +80,59 @@ function ImageContentRenderer({ content, className }: ContentRendererProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to convert base64 (or data URI) to blob URL for better browser compatibility
|
||||
function useBase64ToBlobUrl(data: string | undefined, mimeType: string): string | null {
|
||||
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
setBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle both data URI format and raw base64
|
||||
let base64Data: string;
|
||||
if (data.startsWith('data:')) {
|
||||
// Extract base64 from data URI (e.g., "data:application/pdf;base64,...")
|
||||
const parts = data.split(',');
|
||||
if (parts.length !== 2) {
|
||||
setBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
base64Data = parts[1];
|
||||
} else {
|
||||
// Raw base64 data
|
||||
base64Data = data;
|
||||
}
|
||||
|
||||
const binaryString = atob(base64Data);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
const blob = new Blob([bytes], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
setBlobUrl(url);
|
||||
|
||||
// Cleanup on unmount or when data changes
|
||||
return () => {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to convert base64 to blob URL:', error);
|
||||
setBlobUrl(null);
|
||||
}
|
||||
}, [data, mimeType]);
|
||||
|
||||
return blobUrl;
|
||||
}
|
||||
|
||||
// File content renderer (handles both input and output files)
|
||||
function FileContentRenderer({ content, className }: ContentRendererProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
|
||||
if (content.type !== "input_file" && content.type !== "output_file") return null;
|
||||
|
||||
const fileUrl = content.file_url || content.file_data;
|
||||
@@ -91,31 +142,73 @@ function FileContentRenderer({ content, className }: ContentRendererProps) {
|
||||
const isPdf = filename?.toLowerCase().endsWith(".pdf") || fileUrl?.includes("application/pdf");
|
||||
const isAudio = filename?.toLowerCase().match(/\.(mp3|wav|m4a|ogg|flac|aac)$/);
|
||||
|
||||
// For PDFs, try to embed
|
||||
// Convert base64 to blob URL for PDFs (better browser compatibility)
|
||||
// Use file_data (raw base64) if available, otherwise try file_url
|
||||
const pdfData = isPdf ? (content.file_data || content.file_url) : undefined;
|
||||
const pdfBlobUrl = useBase64ToBlobUrl(pdfData, 'application/pdf');
|
||||
|
||||
// Use blob URL if available, otherwise fall back to original URL
|
||||
const effectivePdfUrl = pdfBlobUrl || fileUrl;
|
||||
|
||||
// Helper to open PDF in new tab
|
||||
const openPdfInNewTab = () => {
|
||||
if (effectivePdfUrl) {
|
||||
window.open(effectivePdfUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// For PDFs - show a clean card with actions (inline preview is unreliable across browsers)
|
||||
if (isPdf && fileUrl) {
|
||||
return (
|
||||
<div className={`my-2 ${className || ""}`}>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<iframe
|
||||
src={fileUrl}
|
||||
className="w-full h-96"
|
||||
title={filename}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">{filename}</span>
|
||||
{fileUrl && (
|
||||
<a
|
||||
href={fileUrl}
|
||||
download={filename}
|
||||
className="ml-auto text-xs text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
<Download className="h-3 w-3" />
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
{/* Header with filename and controls */}
|
||||
<div className="flex items-center gap-2 mb-2 px-1">
|
||||
<FileText className="h-4 w-4 text-red-500" />
|
||||
<span className="text-sm font-medium truncate flex-1">{filename}</span>
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
Collapse
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
Expand
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* PDF Card with actions */}
|
||||
{isExpanded && (
|
||||
<div className="border rounded-lg p-6 bg-muted/50 flex flex-col items-center justify-center gap-4">
|
||||
<FileText className="h-16 w-16 text-red-400" />
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-medium mb-1">{filename}</p>
|
||||
<p className="text-xs text-muted-foreground">PDF Document</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={openPdfInNewTab}
|
||||
className="text-sm bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 px-4 py-2 rounded-md transition-colors"
|
||||
>
|
||||
Open in new tab
|
||||
</button>
|
||||
<a
|
||||
href={effectivePdfUrl || fileUrl}
|
||||
download={filename}
|
||||
className="text-sm text-foreground hover:bg-accent flex items-center gap-2 px-4 py-2 border rounded-md transition-colors"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user