@@ -167,7 +193,7 @@ export function DocumentList({
onClick={() => onSelectDocument(doc)}
>
{/* Icon - color indicates status */}
-
+ {getDocumentIcon(doc, cn("h-4 w-4 flex-shrink-0", styles.icon))}
{/* File name - takes full available space */}
diff --git a/frontend/src/components/DocumentSidebar.tsx b/frontend/src/components/DocumentSidebar.tsx
index 21ecf32..7788295 100644
--- a/frontend/src/components/DocumentSidebar.tsx
+++ b/frontend/src/components/DocumentSidebar.tsx
@@ -38,6 +38,8 @@ export interface DocumentSidebarProps {
onCancelUpload: (fileName: string) => void;
/** Callback to retry a failed upload (kept for interface compatibility) */
onRetryUpload: (fileName: string) => void;
+ /** Callback when a URL is submitted for ingestion */
+ onUrlSubmit?: (url: string, sourceType: 'url' | 'youtube' | 'pdf') => void;
}
/**
@@ -60,6 +62,7 @@ export function DocumentSidebar({
onUploadError,
onCancelUpload: _onCancelUpload = () => { }, // eslint-disable-line @typescript-eslint/no-unused-vars -- Kept for interface compatibility
onRetryUpload: _onRetryUpload = () => { }, // eslint-disable-line @typescript-eslint/no-unused-vars -- Kept for interface compatibility
+ onUrlSubmit,
}: DocumentSidebarProps) {
const [isCollapsed, setIsCollapsed] = useLocalStorage('sidebar-collapsed', false);
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
@@ -139,6 +142,7 @@ export function DocumentSidebar({
onFilesSelected={onFilesSelected}
onUploadComplete={onUploadComplete}
onUploadError={onUploadError}
+ onUrlSubmit={onUrlSubmit}
/>
>
);
diff --git a/frontend/src/components/UploadModal.tsx b/frontend/src/components/UploadModal.tsx
index 66a23e6..adf5f6a 100644
--- a/frontend/src/components/UploadModal.tsx
+++ b/frontend/src/components/UploadModal.tsx
@@ -2,11 +2,13 @@
* UploadModal component
*
* Modal dialog wrapping the IngestionDropzone for document uploads.
+ * Also includes URL input for web page, YouTube, and PDF ingestion.
* Triggered from the sidebar "Add Source" button per UX spec.
*/
'use client';
+import { useState } from 'react';
import {
Dialog,
DialogContent,
@@ -14,7 +16,12 @@ import {
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
import { IngestionDropzone } from '@/components/IngestionDropzone';
+import { Globe, Play, FileType } from 'lucide-react';
+import { detectUrlType } from '@/lib/documents';
import type { Document } from '@/lib/documents';
export interface UploadModalProps {
@@ -30,11 +37,27 @@ export interface UploadModalProps {
onUploadComplete: (documents: Document[]) => void;
/** Callback when upload fails */
onUploadError: (error: Error) => void;
+ /** Callback when URL is submitted for ingestion */
+ onUrlSubmit?: (url: string, sourceType: 'url' | 'youtube' | 'pdf') => void;
+}
+
+/**
+ * Get the URL type icon component based on detected type
+ */
+function UrlTypeIcon({ urlType }: { urlType: 'youtube' | 'pdf' | 'url' }) {
+ switch (urlType) {
+ case 'youtube':
+ return ;
+ case 'pdf':
+ return ;
+ default:
+ return ;
+ }
}
/**
* Modal dialog for document upload.
- * Wraps IngestionDropzone in a Dialog component.
+ * Wraps IngestionDropzone in a Dialog component with URL input above.
*/
export function UploadModal({
open,
@@ -43,13 +66,26 @@ export function UploadModal({
onFilesSelected,
onUploadComplete,
onUploadError,
+ onUrlSubmit,
}: UploadModalProps) {
+ const [urlValue, setUrlValue] = useState('');
+
const handleFilesSelected = (files: File[]) => {
onFilesSelected(files);
// Close modal after files are selected (upload will continue in background)
onOpenChange(false);
};
+ const handleUrlSubmit = () => {
+ const trimmed = urlValue.trim();
+ if (!trimmed || !onUrlSubmit) return;
+ const sourceType = detectUrlType(trimmed);
+ onUrlSubmit(trimmed, sourceType);
+ setUrlValue('');
+ };
+
+ const detectedType = urlValue.trim() ? detectUrlType(urlValue) : null;
+
return (