diff --git a/backend/alembic/versions/b8d2f4a6c091_drop_file_uploads_session_id_fk.py b/backend/alembic/versions/b8d2f4a6c091_drop_file_uploads_session_id_fk.py new file mode 100644 index 00000000..82b8c674 --- /dev/null +++ b/backend/alembic/versions/b8d2f4a6c091_drop_file_uploads_session_id_fk.py @@ -0,0 +1,34 @@ +"""drop file_uploads session_id foreign key constraint + +Revision ID: b8d2f4a6c091 +Revises: a7c9e3b1f402 +Create Date: 2026-03-20 00:00:00.000000 + +The session_id column on file_uploads previously referenced ai_sessions.id. +Removing the FK allows the column to reference either AI sessions or regular +sessions without a constraint violation, while keeping the index for query +performance. +""" +from typing import Sequence, Union + +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = 'b8d2f4a6c091' +down_revision: Union[str, None] = 'a7c9e3b1f402' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.drop_constraint('file_uploads_session_id_fkey', 'file_uploads', type_='foreignkey') + + +def downgrade() -> None: + op.create_foreign_key( + 'file_uploads_session_id_fkey', + 'file_uploads', 'ai_sessions', + ['session_id'], ['id'], + ondelete='SET NULL', + ) diff --git a/backend/app/api/endpoints/ai_sessions.py b/backend/app/api/endpoints/ai_sessions.py index d2ee73ca..3fb43e15 100644 --- a/backend/app/api/endpoints/ai_sessions.py +++ b/backend/app/api/endpoints/ai_sessions.py @@ -562,7 +562,7 @@ async def list_sessions( if confidence_tier: query = query.where(AISession.confidence_tier == confidence_tier) if ticket_id: - query = query.where(AISession.ticket_id == ticket_id) + query = query.where(AISession.psa_ticket_id == ticket_id) if date_from: query = query.where(AISession.created_at >= date_from) if date_to: diff --git a/backend/app/models/file_upload.py b/backend/app/models/file_upload.py index 4c447326..6846b1a3 100644 --- a/backend/app/models/file_upload.py +++ b/backend/app/models/file_upload.py @@ -21,7 +21,7 @@ class FileUpload(Base): UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=False ) session_id: Mapped[Optional[uuid.UUID]] = mapped_column( - UUID(as_uuid=True), ForeignKey("ai_sessions.id", ondelete="SET NULL"), nullable=True, index=True + UUID(as_uuid=True), nullable=True, index=True ) filename: Mapped[str] = mapped_column(String(255), nullable=False) content_type: Mapped[str] = mapped_column(String(100), nullable=False) diff --git a/frontend/src/components/admin/AdminSidebar.tsx b/frontend/src/components/admin/AdminSidebar.tsx index ca77d73c..a5a23d08 100644 --- a/frontend/src/components/admin/AdminSidebar.tsx +++ b/frontend/src/components/admin/AdminSidebar.tsx @@ -10,6 +10,7 @@ import { FolderTree, ClipboardList, MessageSquareText, + LayoutGrid, ArrowLeft, } from 'lucide-react' import { cn } from '@/lib/utils' @@ -25,6 +26,7 @@ const navItems = [ { path: '/admin/categories', label: 'Categories', icon: FolderTree }, { path: '/admin/survey-invites', label: 'Survey Invites', icon: ClipboardList }, { path: '/admin/survey-responses', label: 'Survey Responses', icon: MessageSquareText }, + { path: '/admin/gallery', label: 'Gallery', icon: LayoutGrid }, ] interface AdminSidebarProps { diff --git a/frontend/src/components/common/RichTextInput.tsx b/frontend/src/components/common/RichTextInput.tsx index 9d7a23dc..914726cd 100644 --- a/frontend/src/components/common/RichTextInput.tsx +++ b/frontend/src/components/common/RichTextInput.tsx @@ -73,10 +73,13 @@ export function RichTextInput({ }) }) .catch((err) => { + const errorMsg = err?.response?.status === 503 + ? 'File uploads not available — contact your administrator' + : err?.message || 'Upload failed' setPendingUploads((prev) => prev.map((u) => u.id === upload.id - ? { ...u, status: 'error' as const, error: err?.message || 'Upload failed' } + ? { ...u, status: 'error' as const, error: errorMsg } : u ) ) @@ -196,10 +199,13 @@ export function RichTextInput({ }) }) .catch((err) => { + const errorMsg = err?.response?.status === 503 + ? 'File uploads not available — contact your administrator' + : err?.message || 'Upload failed' setPendingUploads((prev) => prev.map((u) => u.id === uploadId - ? { ...u, status: 'error' as const, error: err?.message || 'Upload failed' } + ? { ...u, status: 'error' as const, error: errorMsg } : u ) ) diff --git a/frontend/src/pages/FlowPilotAnalyticsPage.tsx b/frontend/src/pages/FlowPilotAnalyticsPage.tsx index f1bfe2af..fda132f2 100644 --- a/frontend/src/pages/FlowPilotAnalyticsPage.tsx +++ b/frontend/src/pages/FlowPilotAnalyticsPage.tsx @@ -189,7 +189,7 @@ export default function FlowPilotAnalyticsPage() { {/* Tab bar */} -
+
{TABS.map((tab) => (
{/* Clear filters */} - {(aiFilters.q || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to) && ( + {(aiSearchInput || aiFilters.q || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to) && (
) : aiSessions.length === 0 ? ( - (aiFilters.q || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to) ? ( + (aiSearchInput || aiFilters.q || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to) ? ( setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })} + onClick={() => { + setAiSearchInput('') + setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' }) + }} className="text-foreground hover:underline text-sm" > Clear all filters