Vokturz commited on
Commit
25e8265
·
1 Parent(s): 5cf6fe3

Improve modal animations and UI interactions

Browse files
src/App.tsx CHANGED
@@ -88,7 +88,7 @@ function App() {
88
  {modelInfo?.readme && (
89
  <ModelReadme
90
  readme={modelInfo.readme}
91
- modelName={modelInfo.name}
92
  isModalOpen={isModalOpen}
93
  setIsModalOpen={setIsModalOpen}
94
  />
 
88
  {modelInfo?.readme && (
89
  <ModelReadme
90
  readme={modelInfo.readme}
91
+ modelName={modelInfo.baseId ? modelInfo.baseId : modelInfo.name}
92
  isModalOpen={isModalOpen}
93
  setIsModalOpen={setIsModalOpen}
94
  />
src/components/MarkdownRenderer.tsx CHANGED
@@ -26,7 +26,7 @@ const MarkdownRenderer = ({ content }: MarkdownRendererProps) => {
26
  style={oneLight}
27
  language={match[1]}
28
  PreTag="div"
29
- className="rounded-md my-4 border border-r-2 text-sm"
30
  {...props}
31
  >
32
  {String(children).replace(/\n$/, '')}
 
26
  style={oneLight}
27
  language={match[1]}
28
  PreTag="div"
29
+ className="rounded-md my-4 border text-sm"
30
  {...props}
31
  >
32
  {String(children).replace(/\n$/, '')}
src/components/Modal.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from 'react'
2
  import { X } from 'lucide-react'
3
 
4
  interface ModalProps {
@@ -26,6 +26,11 @@ const Modal: React.FC<ModalProps> = ({
26
  children,
27
  maxWidth = '4xl'
28
  }) => {
 
 
 
 
 
29
  useEffect(() => {
30
  const handleEscape = (e: KeyboardEvent) => {
31
  if (e.key === 'Escape') {
@@ -34,17 +39,24 @@ const Modal: React.FC<ModalProps> = ({
34
  }
35
 
36
  if (isOpen) {
37
- document.addEventListener('keydown', handleEscape)
38
  document.body.style.overflow = 'hidden'
39
- }
40
-
41
- return () => {
42
- document.removeEventListener('keydown', handleEscape)
43
- document.body.style.overflow = 'unset'
 
 
 
 
 
 
44
  }
45
  }, [isOpen, onClose])
46
 
47
- if (!isOpen) return null
 
48
 
49
  const maxWidthClasses = {
50
  sm: 'max-w-sm',
@@ -63,22 +75,30 @@ const Modal: React.FC<ModalProps> = ({
63
  <div className="fixed inset-0 z-50 overflow-y-auto">
64
  {/* Backdrop */}
65
  <div
66
- className="fixed inset-0 bg-black opacity-50 transition-opacity"
 
 
67
  onClick={onClose}
68
  />
69
 
70
  {/* Modal */}
71
  <div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
72
  <div
73
- className={`relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full ${maxWidthClasses[maxWidth]}`}
 
 
 
 
 
 
74
  onClick={(e) => e.stopPropagation()}
75
  >
76
  {/* Header */}
77
- <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
78
  <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
79
  <button
80
  onClick={onClose}
81
- className="rounded-md p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 focus:outline-hidden focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
82
  >
83
  <span className="sr-only">Close</span>
84
  <X className="h-5 w-5" />
@@ -86,7 +106,7 @@ const Modal: React.FC<ModalProps> = ({
86
  </div>
87
 
88
  {/* Content */}
89
- <div className="px-6 py-4 max-h-[calc(100vh-200px)] overflow-y-auto">
90
  {children}
91
  </div>
92
  </div>
 
1
+ import React, { useState, useEffect } from 'react'
2
  import { X } from 'lucide-react'
3
 
4
  interface ModalProps {
 
26
  children,
27
  maxWidth = '4xl'
28
  }) => {
29
+ // State to control if the modal is in the DOM
30
+ const [isRendered, setIsRendered] = useState(isOpen)
31
+ // State to control the animation classes
32
+ const [isAnimating, setIsAnimating] = useState(false)
33
+
34
  useEffect(() => {
35
  const handleEscape = (e: KeyboardEvent) => {
36
  if (e.key === 'Escape') {
 
39
  }
40
 
41
  if (isOpen) {
42
+ setIsRendered(true)
43
  document.body.style.overflow = 'hidden'
44
+ document.addEventListener('keydown', handleEscape)
45
+ const animationTimeout = setTimeout(() => setIsAnimating(true), 20)
46
+ return () => clearTimeout(animationTimeout)
47
+ } else {
48
+ setIsAnimating(false)
49
+ const unmountTimeout = setTimeout(() => {
50
+ setIsRendered(false)
51
+ document.body.style.overflow = 'unset'
52
+ document.removeEventListener('keydown', handleEscape)
53
+ }, 300)
54
+ return () => clearTimeout(unmountTimeout)
55
  }
56
  }, [isOpen, onClose])
57
 
58
+ // Unmount the component completely when not rendered
59
+ if (!isRendered) return null
60
 
61
  const maxWidthClasses = {
62
  sm: 'max-w-sm',
 
75
  <div className="fixed inset-0 z-50 overflow-y-auto">
76
  {/* Backdrop */}
77
  <div
78
+ className={`fixed inset-0 bg-black transition-opacity duration-300 ease-in-out ${
79
+ isAnimating ? 'opacity-50' : 'opacity-0'
80
+ }`}
81
  onClick={onClose}
82
  />
83
 
84
  {/* Modal */}
85
  <div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
86
  <div
87
+ className={`relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all duration-300 ease-in-out sm:my-8 sm:w-full ${
88
+ maxWidthClasses[maxWidth]
89
+ } ${
90
+ isAnimating
91
+ ? 'opacity-100 translate-y-0 sm:scale-100'
92
+ : 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
93
+ }`}
94
  onClick={(e) => e.stopPropagation()}
95
  >
96
  {/* Header */}
97
+ <div className="flex items-center justify-between border-b border-gray-200 px-6 py-4">
98
  <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
99
  <button
100
  onClick={onClose}
101
+ className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
102
  >
103
  <span className="sr-only">Close</span>
104
  <X className="h-5 w-5" />
 
106
  </div>
107
 
108
  {/* Content */}
109
+ <div className="max-h-[calc(100vh-200px)] overflow-y-auto px-6 py-4">
110
  {children}
111
  </div>
112
  </div>
src/components/ModelCode.tsx CHANGED
@@ -3,7 +3,7 @@ import Modal from './Modal'
3
  import MarkdownRenderer from './MarkdownRenderer'
4
  import { useModel } from '@/contexts/ModelContext'
5
  import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
6
- import { useState } from 'react'
7
 
8
  interface ModelCodeProps {
9
  isCodeModalOpen: boolean
@@ -12,7 +12,23 @@ interface ModelCodeProps {
12
 
13
  const ModelCode = ({ isCodeModalOpen, setIsCodeModalOpen }: ModelCodeProps) => {
14
  const [isCopied, setIsCopied] = useState(false)
 
 
 
15
  const { modelInfo, pipeline, selectedQuantization } = useModel()
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  if (!modelInfo) return null
17
 
18
  const title = (
@@ -24,7 +40,6 @@ const ModelCode = ({ isCodeModalOpen, setIsCodeModalOpen }: ModelCodeProps) => {
24
  rel="noopener noreferrer"
25
  >
26
  <ExternalLink className="w-3 h-3 inline-block mr-1" />
27
-
28
  {modelInfo.name}
29
  </a>
30
  </div>
@@ -107,7 +122,6 @@ print(result)
107
  setIsCopied(true)
108
  setTimeout(() => setIsCopied(false), 2000)
109
  }
110
-
111
  const pipelineName = pipeline
112
  .split('-')
113
  .map((word, index) => word.charAt(0).toUpperCase() + word.slice(1))
@@ -121,6 +135,7 @@ print(result)
121
  title={title}
122
  maxWidth="5xl"
123
  >
 
124
  <div className="text-sm max-w-none px-4">
125
  <div className="flex flex-row">
126
  <img src="/javascript-logo.svg" className="w-6 h-6 mr-1 rounded" />
@@ -133,7 +148,7 @@ print(result)
133
  target="_blank"
134
  rel="noopener noreferrer"
135
  >
136
- Read about {pipeline} Pipeline Documentation for Javascript
137
  </a>
138
  </div>
139
  <div className="relative">
@@ -159,7 +174,7 @@ print(result)
159
  target="_blank"
160
  rel="noopener noreferrer"
161
  >
162
- Read about {pipeline} Pipeline Documentation for Python
163
  </a>
164
  <div className="relative">
165
  <div className="absolute right-0 top-0 mt-2 mr-2">
@@ -176,8 +191,14 @@ print(result)
176
  </div>
177
  </div>
178
  </div>
179
- {isCopied && (
180
- <div className="absolute top-2 left-1/2 transform -translate-x-1/2">
 
 
 
 
 
 
181
  <Alert>
182
  <CopyCheck className="w-4 h-4 opacity-60" />
183
  <AlertDescription>Copied!</AlertDescription>
 
3
  import MarkdownRenderer from './MarkdownRenderer'
4
  import { useModel } from '@/contexts/ModelContext'
5
  import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
6
+ import { useState, useEffect } from 'react'
7
 
8
  interface ModelCodeProps {
9
  isCodeModalOpen: boolean
 
12
 
13
  const ModelCode = ({ isCodeModalOpen, setIsCodeModalOpen }: ModelCodeProps) => {
14
  const [isCopied, setIsCopied] = useState(false)
15
+ const [showAlert, setShowAlert] = useState(false)
16
+ const [animateAlert, setAnimateAlert] = useState(false)
17
+
18
  const { modelInfo, pipeline, selectedQuantization } = useModel()
19
+
20
+ useEffect(() => {
21
+ if (isCopied) {
22
+ setShowAlert(true)
23
+ const enterTimeout = setTimeout(() => setAnimateAlert(true), 20)
24
+ return () => clearTimeout(enterTimeout)
25
+ } else {
26
+ setAnimateAlert(false)
27
+ const exitTimeout = setTimeout(() => setShowAlert(false), 300) // Match duration-300
28
+ return () => clearTimeout(exitTimeout)
29
+ }
30
+ }, [isCopied])
31
+
32
  if (!modelInfo) return null
33
 
34
  const title = (
 
40
  rel="noopener noreferrer"
41
  >
42
  <ExternalLink className="w-3 h-3 inline-block mr-1" />
 
43
  {modelInfo.name}
44
  </a>
45
  </div>
 
122
  setIsCopied(true)
123
  setTimeout(() => setIsCopied(false), 2000)
124
  }
 
125
  const pipelineName = pipeline
126
  .split('-')
127
  .map((word, index) => word.charAt(0).toUpperCase() + word.slice(1))
 
135
  title={title}
136
  maxWidth="5xl"
137
  >
138
+ {/* ... (all your modal content JSX is unchanged) */}
139
  <div className="text-sm max-w-none px-4">
140
  <div className="flex flex-row">
141
  <img src="/javascript-logo.svg" className="w-6 h-6 mr-1 rounded" />
 
148
  target="_blank"
149
  rel="noopener noreferrer"
150
  >
151
+ Read about {pipeline} in Transformers.js documentation
152
  </a>
153
  </div>
154
  <div className="relative">
 
174
  target="_blank"
175
  rel="noopener noreferrer"
176
  >
177
+ Read about {pipeline} in Transformers documentation
178
  </a>
179
  <div className="relative">
180
  <div className="absolute right-0 top-0 mt-2 mr-2">
 
191
  </div>
192
  </div>
193
  </div>
194
+ {showAlert && (
195
+ <div
196
+ className={`absolute top-4 left-1/2 -translate-x-1/2 transition-all duration-300 ease-in-out ${
197
+ animateAlert
198
+ ? 'opacity-100 translate-y-0'
199
+ : 'opacity-0 -translate-y-4'
200
+ }`}
201
+ >
202
  <Alert>
203
  <CopyCheck className="w-4 h-4 opacity-60" />
204
  <AlertDescription>Copied!</AlertDescription>
src/components/ModelLoader.tsx CHANGED
@@ -75,13 +75,13 @@ const ModelLoader = () => {
75
  output.file.startsWith('onnx')
76
  ) {
77
  setProgress(output.progress)
78
- setShowAlert(true)
79
- setAlertMessage(
80
- <div className="flex items-center">
81
- <Loader2 className="animate-spin h-4 w-4 mr-2" />
82
- Loading Model
83
- </div>
84
- )
85
  }
86
  } else if (status === 'error') {
87
  setStatus('error')
 
75
  output.file.startsWith('onnx')
76
  ) {
77
  setProgress(output.progress)
78
+ // setShowAlert(true)
79
+ // setAlertMessage(
80
+ // <div className="flex items-center">
81
+ // <Loader2 className="animate-spin h-4 w-4 mr-2" />
82
+ // Loading Model
83
+ // </div>
84
+ // )
85
  }
86
  } else if (status === 'error') {
87
  setStatus('error')
src/components/ModelReadme.tsx CHANGED
@@ -27,7 +27,7 @@ const ModelReadme = ({
27
 
28
  {modelName}
29
  </a>
30
- <span className=" text-gray-500">README.md</span>
31
  </div>
32
  )
33
 
 
27
 
28
  {modelName}
29
  </a>
30
+ <span className=" text-gray-300">README.md</span>
31
  </div>
32
  )
33
 
src/components/ModelSelector.tsx CHANGED
@@ -114,7 +114,6 @@ function ModelSelector() {
114
  ),
115
  widgetData: modelInfoResponse.widgetData
116
  }
117
- console.log(modelInfo)
118
  setModelInfo(modelInfo)
119
  setIsCustomModel(isCustom)
120
  setIsFetching(false)
@@ -127,12 +126,17 @@ function ModelSelector() {
127
  [setModelInfo, pipeline, setIsFetching]
128
  )
129
 
130
- // Reset custom model state when pipeline changes
131
  useEffect(() => {
 
 
132
  setIsCustomModel(false)
133
  setShowCustomInput(false)
134
  setCustomModelName('')
135
  setCustomModelError('')
 
 
 
 
136
  }, [pipeline])
137
 
138
  // Update modelInfo to first model when models are loaded and no custom model is selected
 
114
  ),
115
  widgetData: modelInfoResponse.widgetData
116
  }
 
117
  setModelInfo(modelInfo)
118
  setIsCustomModel(isCustom)
119
  setIsFetching(false)
 
126
  [setModelInfo, pipeline, setIsFetching]
127
  )
128
 
 
129
  useEffect(() => {
130
+ // Reset custom model state when pipeline changes
131
+
132
  setIsCustomModel(false)
133
  setShowCustomInput(false)
134
  setCustomModelName('')
135
  setCustomModelError('')
136
+
137
+ if (pipeline !== 'feature-extraction') {
138
+ setSortBy('downloads')
139
+ }
140
  }, [pipeline])
141
 
142
  // Update modelInfo to first model when models are loaded and no custom model is selected
src/components/PipelineSelector.tsx CHANGED
@@ -11,8 +11,8 @@ export const supportedPipelines = [
11
  'feature-extraction',
12
  'image-classification',
13
  'text-generation',
14
- 'zero-shot-classification',
15
- 'text-classification'
16
  // 'summarization',
17
  // 'translation'
18
  ]
 
11
  'feature-extraction',
12
  'image-classification',
13
  'text-generation',
14
+ 'text-classification',
15
+ 'zero-shot-classification'
16
  // 'summarization',
17
  // 'translation'
18
  ]
src/lib/huggingface.ts CHANGED
@@ -132,7 +132,7 @@ const getModelsByPipeline = async (
132
  ): Promise<ModelInfoResponse[]> => {
133
  // Second search with search=onnx
134
  const response1 = await fetch(
135
- `https://huggingface.co/api/models?filter=${pipelineTag}&search=onnx-community&sort=createdAt&limit=10`,
136
  {
137
  method: 'GET'
138
  }
@@ -175,10 +175,10 @@ const getModelsByPipeline = async (
175
  !model.id.includes('ms-marco') &&
176
  !model.id.includes('MiniLM')
177
  )
178
- .slice(0, 20)
179
  }
180
 
181
- return uniqueModels.slice(0, 20)
182
  }
183
 
184
  const getModelsByPipelineCustom = async (
 
132
  ): Promise<ModelInfoResponse[]> => {
133
  // Second search with search=onnx
134
  const response1 = await fetch(
135
+ `https://huggingface.co/api/models?filter=${pipelineTag}&search=onnx-community&sort=createdAt&limit=15`,
136
  {
137
  method: 'GET'
138
  }
 
175
  !model.id.includes('ms-marco') &&
176
  !model.id.includes('MiniLM')
177
  )
178
+ .slice(0, 30)
179
  }
180
 
181
+ return uniqueModels.slice(0, 30)
182
  }
183
 
184
  const getModelsByPipelineCustom = async (