diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx new file mode 100644 index 0000000..ea5757c --- /dev/null +++ b/src/app/docs/layout.tsx @@ -0,0 +1,11 @@ +export default function DocsLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+ {children} +
+ ) +} \ No newline at end of file diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 69e98ba..bc116c2 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -2,16 +2,44 @@ import { useState, useEffect } from 'react' import dynamic from 'next/dynamic' +import './swagger-ui-tailwind.css' // Dynamically import Swagger UI to avoid SSR issues const SwaggerUI = dynamic(() => import('swagger-ui-react'), { ssr: false, - loading: () =>
Loading Swagger UI...
+ loading: () =>
Loading Swagger UI...
}) export default function ApiDocsPage() { const [spec, setSpec] = useState(null) const [error, setError] = useState(null) + const [isDark, setIsDark] = useState(false) + + // Dark mode detection + useEffect(() => { + const checkDarkMode = () => { + setIsDark( + window.matchMedia('(prefers-color-scheme: dark)').matches || + document.documentElement.classList.contains('dark') + ) + } + + checkDarkMode() + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + mediaQuery.addEventListener('change', checkDarkMode) + + // Watch for class changes on html element + const observer = new MutationObserver(checkDarkMode) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }) + + return () => { + mediaQuery.removeEventListener('change', checkDarkMode) + observer.disconnect() + } + }, []) useEffect(() => { fetch('/api/docs') @@ -62,27 +90,162 @@ export default function ApiDocsPage() { } return ( -
-
-
-

- Photo Gallery AI API Documentation -

-

- Complete API reference for the AI-powered photo organization system. - All endpoints run locally with no cloud dependencies. -

+
+ {/* Full-width Header */} +
+
+
+
+
+ API +
+
+

+ Photo Gallery AI API +

+

+ Interactive Documentation +

+
+
+ +

+ Complete API reference for AI-powered photo organization. + Features dual-model classification, image captioning, and comprehensive tag management. +

+ +
+
+
+ 100% Local Processing +
+
+
+ No Cloud Dependencies +
+
+
+ OpenAPI 3.0 +
+
+
- -
- +
+ + {/* Main Content */} +
+
+ {/* Quick Stats */} +
+
+
8+
+
API Endpoints
+
+
+
3
+
AI Models
+
+
+
25+
+
Tags per Photo
+
+
+
+
Privacy First
+
+
+ + {/* Feature Highlights */} +
+
+
+ + + +
+

+ AI Classification +

+

+ ViT + CLIP models for comprehensive object and style recognition +

+
+ +
+
+ + + +
+

+ Image Captioning +

+

+ BLIP model generates natural language descriptions +

+
+ +
+
+ + + +
+

+ Batch Processing +

+

+ Process entire photo libraries with progress tracking +

+
+
+ + {/* API Documentation Section */} +
+ {/* Documentation Content */} +
+ { + return req + }} + responseInterceptor={(res) => { + return res + }} + /> +
+
-
+ + + {/* Footer */} +
+
+
+
+ Generated automatically from code annotations • Always up-to-date +
+
+ +
+ OpenAPI 3.0 +
+ +
+ Interactive Testing +
+
+
+
+
) } \ No newline at end of file diff --git a/src/app/docs/swagger-ui-tailwind.css b/src/app/docs/swagger-ui-tailwind.css new file mode 100644 index 0000000..5bb96a9 --- /dev/null +++ b/src/app/docs/swagger-ui-tailwind.css @@ -0,0 +1,312 @@ +/* Swagger UI Tailwind Integration */ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +/* Root variables using Tailwind theme */ +.swagger-ui { + /* Color scheme */ + --swagger-ui-bg-primary: theme('colors.white'); + --swagger-ui-bg-secondary: theme('colors.gray.50'); + --swagger-ui-bg-tertiary: theme('colors.gray.100'); + --swagger-ui-text-primary: theme('colors.gray.900'); + --swagger-ui-text-secondary: theme('colors.gray.600'); + --swagger-ui-text-muted: theme('colors.gray.400'); + --swagger-ui-border: theme('colors.gray.200'); + --swagger-ui-accent: theme('colors.blue.600'); + --swagger-ui-accent-hover: theme('colors.blue.700'); + --swagger-ui-success: theme('colors.green.600'); + --swagger-ui-warning: theme('colors.yellow.600'); + --swagger-ui-error: theme('colors.red.600'); + + /* Typography */ + --swagger-ui-font-family: theme('fontFamily.sans'); + --swagger-ui-font-mono: theme('fontFamily.mono'); + --swagger-ui-font-size-base: theme('fontSize.sm'); + --swagger-ui-font-size-lg: theme('fontSize.base'); + --swagger-ui-font-size-xl: theme('fontSize.lg'); + + /* Spacing */ + --swagger-ui-spacing-xs: theme('spacing.1'); + --swagger-ui-spacing-sm: theme('spacing.2'); + --swagger-ui-spacing-md: theme('spacing.4'); + --swagger-ui-spacing-lg: theme('spacing.6'); + --swagger-ui-spacing-xl: theme('spacing.8'); + + /* Shadows */ + --swagger-ui-shadow-sm: theme('boxShadow.sm'); + --swagger-ui-shadow-md: theme('boxShadow.md'); + --swagger-ui-shadow-lg: theme('boxShadow.lg'); + + /* Border radius */ + --swagger-ui-radius-sm: theme('borderRadius.sm'); + --swagger-ui-radius-md: theme('borderRadius.md'); + --swagger-ui-radius-lg: theme('borderRadius.lg'); +} + +/* Dark mode variables */ +.dark .swagger-ui { + --swagger-ui-bg-primary: theme('colors.gray.900'); + --swagger-ui-bg-secondary: theme('colors.gray.800'); + --swagger-ui-bg-tertiary: theme('colors.gray.700'); + --swagger-ui-text-primary: theme('colors.white'); + --swagger-ui-text-secondary: theme('colors.gray.300'); + --swagger-ui-text-muted: theme('colors.gray.500'); + --swagger-ui-border: theme('colors.gray.600'); + --swagger-ui-accent: theme('colors.blue.400'); + --swagger-ui-accent-hover: theme('colors.blue.300'); + --swagger-ui-success: theme('colors.green.400'); + --swagger-ui-warning: theme('colors.yellow.400'); + --swagger-ui-error: theme('colors.red.400'); +} + +/* Base styling with Tailwind utilities */ +.swagger-ui { + @apply font-sans text-sm; + background-color: var(--swagger-ui-bg-primary); + color: var(--swagger-ui-text-primary); + font-family: var(--swagger-ui-font-family); +} + +/* Header styling */ +.swagger-ui .info { + @apply border-b border-gray-200 dark:border-gray-700 pb-6 mb-8; +} + +.swagger-ui .info .title { + @apply text-3xl font-bold text-gray-900 dark:text-white mb-4; + color: var(--swagger-ui-text-primary); +} + +.swagger-ui .info .description { + @apply text-gray-600 dark:text-gray-300 leading-relaxed; + color: var(--swagger-ui-text-secondary); +} + +/* Operation blocks */ +.swagger-ui .opblock { + @apply border border-gray-200 dark:border-gray-700 rounded-lg mb-4 overflow-hidden shadow-sm; + background-color: var(--swagger-ui-bg-primary); + border-color: var(--swagger-ui-border); + box-shadow: var(--swagger-ui-shadow-sm); + border-radius: var(--swagger-ui-radius-lg); +} + +.swagger-ui .opblock .opblock-summary { + @apply px-4 py-3 cursor-pointer transition-colors duration-200; + background-color: var(--swagger-ui-bg-secondary); +} + +.swagger-ui .opblock .opblock-summary:hover { + @apply bg-gray-100 dark:bg-gray-700; + background-color: var(--swagger-ui-bg-tertiary); +} + +/* HTTP method colors */ +.swagger-ui .opblock.opblock-get .opblock-summary { + @apply bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-500; +} + +.swagger-ui .opblock.opblock-post .opblock-summary { + @apply bg-green-50 dark:bg-green-900/20 border-l-4 border-green-500; +} + +.swagger-ui .opblock.opblock-put .opblock-summary { + @apply bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-500; +} + +.swagger-ui .opblock.opblock-delete .opblock-summary { + @apply bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500; +} + +.swagger-ui .opblock.opblock-patch .opblock-summary { + @apply bg-purple-50 dark:bg-purple-900/20 border-l-4 border-purple-500; +} + +/* Method labels */ +.swagger-ui .opblock .opblock-summary-method { + @apply px-3 py-1 rounded text-white text-xs font-semibold uppercase; + border-radius: var(--swagger-ui-radius-sm); +} + +.swagger-ui .opblock.opblock-get .opblock-summary-method { + @apply bg-blue-600 dark:bg-blue-500; +} + +.swagger-ui .opblock.opblock-post .opblock-summary-method { + @apply bg-green-600 dark:bg-green-500; +} + +.swagger-ui .opblock.opblock-put .opblock-summary-method { + @apply bg-yellow-600 dark:bg-yellow-500; +} + +.swagger-ui .opblock.opblock-delete .opblock-summary-method { + @apply bg-red-600 dark:bg-red-500; +} + +.swagger-ui .opblock.opblock-patch .opblock-summary-method { + @apply bg-purple-600 dark:bg-purple-500; +} + +/* Operation summary text */ +.swagger-ui .opblock-summary-path { + @apply font-mono text-sm text-gray-700 dark:text-gray-300; + font-family: var(--swagger-ui-font-mono); +} + +.swagger-ui .opblock-summary-description { + @apply text-gray-600 dark:text-gray-400 text-sm ml-4; +} + +/* Parameters and request body */ +.swagger-ui .parameters-container { + @apply bg-gray-50 dark:bg-gray-800 p-4; + background-color: var(--swagger-ui-bg-secondary); +} + +.swagger-ui .parameter__name { + @apply font-mono text-sm font-medium text-gray-900 dark:text-white; + font-family: var(--swagger-ui-font-mono); + color: var(--swagger-ui-text-primary); +} + +.swagger-ui .parameter__type { + @apply text-blue-600 dark:text-blue-400 text-xs; + color: var(--swagger-ui-accent); +} + +.swagger-ui .parameter__deprecated { + @apply line-through text-gray-400; +} + +/* Response section */ +.swagger-ui .responses-wrapper { + @apply border-t border-gray-200 dark:border-gray-700; + border-color: var(--swagger-ui-border); +} + +.swagger-ui .response-col_status { + @apply font-mono font-bold; + font-family: var(--swagger-ui-font-mono); +} + +/* Status code colors */ +.swagger-ui .response-col_status .response-undocumented { + @apply text-gray-600 dark:text-gray-400; +} + +.swagger-ui .responses-table td { + @apply p-3 border-b border-gray-100 dark:border-gray-700; + border-color: var(--swagger-ui-border); +} + +/* Try it out button */ +.swagger-ui .btn.try-out__btn { + @apply bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors; + background-color: var(--swagger-ui-accent); + border-radius: var(--swagger-ui-radius-md); +} + +.swagger-ui .btn.try-out__btn:hover { + background-color: var(--swagger-ui-accent-hover); +} + +.swagger-ui .btn.execute { + @apply bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-md font-medium transition-colors; + background-color: var(--swagger-ui-success); +} + +.swagger-ui .btn.cancel { + @apply bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md transition-colors; +} + +.swagger-ui .btn.clear { + @apply bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition-colors; +} + +/* Input fields */ +.swagger-ui input[type=text], +.swagger-ui input[type=email], +.swagger-ui input[type=password], +.swagger-ui textarea, +.swagger-ui select { + @apply border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent; + border-color: var(--swagger-ui-border); + background-color: var(--swagger-ui-bg-primary); + color: var(--swagger-ui-text-primary); + border-radius: var(--swagger-ui-radius-md); +} + +/* Code blocks */ +.swagger-ui .highlight-code, +.swagger-ui .microlight { + @apply bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md p-4 font-mono text-sm; + background-color: var(--swagger-ui-bg-secondary); + border-color: var(--swagger-ui-border); + font-family: var(--swagger-ui-font-mono); + border-radius: var(--swagger-ui-radius-md); +} + +/* Models section */ +.swagger-ui .model-container { + @apply bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg; + background-color: var(--swagger-ui-bg-secondary); + border-color: var(--swagger-ui-border); + border-radius: var(--swagger-ui-radius-lg); +} + +.swagger-ui .model-box { + @apply p-4; +} + +.swagger-ui .model .property { + @apply py-2 border-b border-gray-100 dark:border-gray-700; + border-color: var(--swagger-ui-border); +} + +/* Schema section */ +.swagger-ui .model-title { + @apply text-lg font-semibold text-gray-900 dark:text-white mb-2; + color: var(--swagger-ui-text-primary); +} + +/* Tags */ +.swagger-ui .opblock-tag { + @apply text-2xl font-bold text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2 mb-4; + color: var(--swagger-ui-text-primary); + border-color: var(--swagger-ui-border); +} + +/* Authorization */ +.swagger-ui .auth-container { + @apply bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4; + border-radius: var(--swagger-ui-radius-lg); +} + +/* Loading and errors */ +.swagger-ui .loading-container { + @apply text-center py-8; +} + +.swagger-ui .errors-wrapper { + @apply bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 text-red-700 dark:text-red-300; + background-color: color-mix(in srgb, var(--swagger-ui-error) 10%, transparent); + border-color: var(--swagger-ui-error); + border-radius: var(--swagger-ui-radius-lg); +} + +/* Responsive design */ +@media (max-width: 768px) { + .swagger-ui .opblock-summary { + @apply px-3 py-2; + } + + .swagger-ui .info .title { + @apply text-2xl; + } + + .swagger-ui .parameters-container { + @apply p-3; + } +} \ No newline at end of file diff --git a/src/components/ApiDocsLink.tsx b/src/components/ApiDocsLink.tsx new file mode 100644 index 0000000..385b7c1 --- /dev/null +++ b/src/components/ApiDocsLink.tsx @@ -0,0 +1,35 @@ +'use client' + +import { useState } from 'react' +import { IconApi, IconBook, IconExternalLink } from '@tabler/icons-react' + +export default function ApiDocsLink() { + const [showTooltip, setShowTooltip] = useState(false) + + return ( +
+ setShowTooltip(true)} + onMouseLeave={() => setShowTooltip(false)} + > + + API Docs + + + + {showTooltip && ( +
+
+ + Interactive API Documentation +
+
+
+ )} +
+ ) +} \ No newline at end of file