update layout adding a Modal and Sidebar
Browse files- package-lock.json +41 -0
- package.json +1 -0
- src/App.tsx +45 -54
- src/components/MarkdownRenderer.tsx +1 -1
- src/components/Modal.tsx +98 -0
- src/components/ModelInfo.tsx +34 -40
- src/components/ModelLoader.tsx +2 -7
- src/components/ModelReadme.tsx +41 -24
- src/components/ModelSelector.tsx +43 -35
- src/components/Sidebar.tsx +83 -0
- src/components/TextClassification.tsx +34 -9
- src/components/Tooltip.tsx +1 -1
- src/types.ts +3 -1
- tailwind.config.js +3 -3
package-lock.json
CHANGED
@@ -10,6 +10,7 @@
|
|
10 |
"dependencies": {
|
11 |
"@headlessui/react": "^2.2.4",
|
12 |
"@huggingface/transformers": "^3.6.1",
|
|
|
13 |
"@testing-library/dom": "^10.4.0",
|
14 |
"@testing-library/jest-dom": "^6.6.3",
|
15 |
"@testing-library/react": "^16.3.0",
|
@@ -4167,6 +4168,34 @@
|
|
4167 |
"tslib": "^2.8.0"
|
4168 |
}
|
4169 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4170 |
"node_modules/@tanstack/react-virtual": {
|
4171 |
"version": "3.13.12",
|
4172 |
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
@@ -12572,12 +12601,24 @@
|
|
12572 |
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
12573 |
"license": "MIT"
|
12574 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
12575 |
"node_modules/lodash.debounce": {
|
12576 |
"version": "4.0.8",
|
12577 |
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
12578 |
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
12579 |
"license": "MIT"
|
12580 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
12581 |
"node_modules/lodash.memoize": {
|
12582 |
"version": "4.1.2",
|
12583 |
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
|
|
10 |
"dependencies": {
|
11 |
"@headlessui/react": "^2.2.4",
|
12 |
"@huggingface/transformers": "^3.6.1",
|
13 |
+
"@tailwindcss/typography": "^0.5.16",
|
14 |
"@testing-library/dom": "^10.4.0",
|
15 |
"@testing-library/jest-dom": "^6.6.3",
|
16 |
"@testing-library/react": "^16.3.0",
|
|
|
4168 |
"tslib": "^2.8.0"
|
4169 |
}
|
4170 |
},
|
4171 |
+
"node_modules/@tailwindcss/typography": {
|
4172 |
+
"version": "0.5.16",
|
4173 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
4174 |
+
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
|
4175 |
+
"license": "MIT",
|
4176 |
+
"dependencies": {
|
4177 |
+
"lodash.castarray": "^4.4.0",
|
4178 |
+
"lodash.isplainobject": "^4.0.6",
|
4179 |
+
"lodash.merge": "^4.6.2",
|
4180 |
+
"postcss-selector-parser": "6.0.10"
|
4181 |
+
},
|
4182 |
+
"peerDependencies": {
|
4183 |
+
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
4184 |
+
}
|
4185 |
+
},
|
4186 |
+
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
4187 |
+
"version": "6.0.10",
|
4188 |
+
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
4189 |
+
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
4190 |
+
"license": "MIT",
|
4191 |
+
"dependencies": {
|
4192 |
+
"cssesc": "^3.0.0",
|
4193 |
+
"util-deprecate": "^1.0.2"
|
4194 |
+
},
|
4195 |
+
"engines": {
|
4196 |
+
"node": ">=4"
|
4197 |
+
}
|
4198 |
+
},
|
4199 |
"node_modules/@tanstack/react-virtual": {
|
4200 |
"version": "3.13.12",
|
4201 |
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
|
|
12601 |
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
12602 |
"license": "MIT"
|
12603 |
},
|
12604 |
+
"node_modules/lodash.castarray": {
|
12605 |
+
"version": "4.4.0",
|
12606 |
+
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
12607 |
+
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
12608 |
+
"license": "MIT"
|
12609 |
+
},
|
12610 |
"node_modules/lodash.debounce": {
|
12611 |
"version": "4.0.8",
|
12612 |
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
12613 |
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
12614 |
"license": "MIT"
|
12615 |
},
|
12616 |
+
"node_modules/lodash.isplainobject": {
|
12617 |
+
"version": "4.0.6",
|
12618 |
+
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
12619 |
+
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
12620 |
+
"license": "MIT"
|
12621 |
+
},
|
12622 |
"node_modules/lodash.memoize": {
|
12623 |
"version": "4.1.2",
|
12624 |
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
package.json
CHANGED
@@ -5,6 +5,7 @@
|
|
5 |
"dependencies": {
|
6 |
"@headlessui/react": "^2.2.4",
|
7 |
"@huggingface/transformers": "^3.6.1",
|
|
|
8 |
"@testing-library/dom": "^10.4.0",
|
9 |
"@testing-library/jest-dom": "^6.6.3",
|
10 |
"@testing-library/react": "^16.3.0",
|
|
|
5 |
"dependencies": {
|
6 |
"@headlessui/react": "^2.2.4",
|
7 |
"@huggingface/transformers": "^3.6.1",
|
8 |
+
"@tailwindcss/typography": "^0.5.16",
|
9 |
"@testing-library/dom": "^10.4.0",
|
10 |
"@testing-library/jest-dom": "^6.6.3",
|
11 |
"@testing-library/react": "^16.3.0",
|
src/App.tsx
CHANGED
@@ -1,24 +1,18 @@
|
|
1 |
-
import { useEffect } from 'react'
|
2 |
-
import
|
3 |
import ZeroShotClassification from './components/ZeroShotClassification'
|
4 |
import TextClassification from './components/TextClassification'
|
5 |
import Header from './Header'
|
6 |
import { useModel } from './contexts/ModelContext'
|
7 |
import { getModelsByPipeline } from './lib/huggingface'
|
8 |
-
import ModelSelector from './components/ModelSelector'
|
9 |
-
import ModelInfo from './components/ModelInfo'
|
10 |
-
import ModelReadme from './components/ModelReadme'
|
11 |
import TextGeneration from './components/TextGeneration'
|
|
|
|
|
12 |
|
13 |
function App() {
|
14 |
-
const
|
15 |
-
|
16 |
-
|
17 |
-
setModels,
|
18 |
-
setModelInfo,
|
19 |
-
modelInfo,
|
20 |
-
setIsFetching
|
21 |
-
} = useModel()
|
22 |
|
23 |
useEffect(() => {
|
24 |
setModelInfo(null)
|
@@ -41,52 +35,49 @@ function App() {
|
|
41 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
42 |
<Header />
|
43 |
|
44 |
-
<
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
pipeline={pipeline}
|
55 |
-
setPipeline={setPipeline}
|
56 |
/>
|
57 |
-
|
58 |
-
|
59 |
-
<div className="space-y-2">
|
60 |
-
<span className="text-lg font-semibold text-gray-900 block">
|
61 |
-
Select Model
|
62 |
-
</span>
|
63 |
-
<ModelSelector />
|
64 |
-
</div>
|
65 |
-
</div>
|
66 |
-
|
67 |
-
<div className="ml-6">
|
68 |
-
<ModelInfo />
|
69 |
</div>
|
70 |
</div>
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
/>
|
78 |
-
|
79 |
</div>
|
80 |
-
</
|
81 |
-
|
82 |
-
<
|
83 |
-
{
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
{pipeline === 'text-generation' && <TextGeneration />}
|
88 |
-
</div>
|
89 |
-
</main>
|
90 |
</div>
|
91 |
)
|
92 |
}
|
|
|
1 |
+
import { useEffect, useState } from 'react'
|
2 |
+
import { Settings } from 'lucide-react'
|
3 |
import ZeroShotClassification from './components/ZeroShotClassification'
|
4 |
import TextClassification from './components/TextClassification'
|
5 |
import Header from './Header'
|
6 |
import { useModel } from './contexts/ModelContext'
|
7 |
import { getModelsByPipeline } from './lib/huggingface'
|
|
|
|
|
|
|
8 |
import TextGeneration from './components/TextGeneration'
|
9 |
+
import Sidebar from './components/Sidebar'
|
10 |
+
import ModelReadme from './components/ModelReadme'
|
11 |
|
12 |
function App() {
|
13 |
+
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
|
14 |
+
const { pipeline, setModels, setModelInfo, modelInfo, setIsFetching } =
|
15 |
+
useModel()
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
useEffect(() => {
|
18 |
setModelInfo(null)
|
|
|
35 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
36 |
<Header />
|
37 |
|
38 |
+
<div className="flex h-[calc(100vh-4rem)]">
|
39 |
+
{/* Header is h-16 = 4rem */}
|
40 |
+
{/* Main Content */}
|
41 |
+
<main className="flex-1 overflow-auto">
|
42 |
+
<div className="h-full px-4 sm:px-6 lg:px-8 py-8 lg:pr-4 max-w-none">
|
43 |
+
{/* Mobile menu button */}
|
44 |
+
<div className="flex flex-row space-x-4">
|
45 |
+
<div className="lg:hidden mb-4">
|
46 |
+
<button
|
47 |
+
onClick={() => setIsSidebarOpen(true)}
|
48 |
+
className="inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
49 |
+
>
|
50 |
+
<Settings className="w-4 h-4 mr-2" />
|
51 |
+
Configuration
|
52 |
+
</button>
|
53 |
+
</div>
|
54 |
+
{/* Model README Button */}
|
55 |
+
<div className="mb-4">
|
56 |
+
{modelInfo?.readme && (
|
57 |
+
<ModelReadme
|
58 |
+
readme={modelInfo.readme}
|
59 |
+
modelName={modelInfo.name}
|
60 |
pipeline={pipeline}
|
|
|
61 |
/>
|
62 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
</div>
|
64 |
</div>
|
65 |
+
{/* Pipeline Component */}
|
66 |
+
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
|
67 |
+
{pipeline === 'zero-shot-classification' && (
|
68 |
+
<ZeroShotClassification />
|
69 |
+
)}
|
70 |
+
{pipeline === 'text-classification' && <TextClassification />}
|
71 |
+
{pipeline === 'text-generation' && <TextGeneration />}
|
72 |
+
</div>
|
73 |
</div>
|
74 |
+
</main>
|
75 |
+
{/* Sidebar */}
|
76 |
+
<Sidebar
|
77 |
+
isOpen={isSidebarOpen}
|
78 |
+
onClose={() => setIsSidebarOpen(false)}
|
79 |
+
/>
|
80 |
+
</div>
|
|
|
|
|
|
|
81 |
</div>
|
82 |
)
|
83 |
}
|
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"
|
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 border-r-2 text-sm"
|
30 |
{...props}
|
31 |
>
|
32 |
{String(children).replace(/\n$/, '')}
|
src/components/Modal.tsx
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect } from 'react'
|
2 |
+
import { X } from 'lucide-react'
|
3 |
+
|
4 |
+
interface ModalProps {
|
5 |
+
isOpen: boolean
|
6 |
+
onClose: () => void
|
7 |
+
title: React.ReactNode
|
8 |
+
children: React.ReactNode
|
9 |
+
maxWidth?:
|
10 |
+
| 'sm'
|
11 |
+
| 'md'
|
12 |
+
| 'lg'
|
13 |
+
| 'xl'
|
14 |
+
| '2xl'
|
15 |
+
| '3xl'
|
16 |
+
| '4xl'
|
17 |
+
| '5xl'
|
18 |
+
| '6xl'
|
19 |
+
| '7xl'
|
20 |
+
}
|
21 |
+
|
22 |
+
const Modal: React.FC<ModalProps> = ({
|
23 |
+
isOpen,
|
24 |
+
onClose,
|
25 |
+
title,
|
26 |
+
children,
|
27 |
+
maxWidth = '4xl'
|
28 |
+
}) => {
|
29 |
+
useEffect(() => {
|
30 |
+
const handleEscape = (e: KeyboardEvent) => {
|
31 |
+
if (e.key === 'Escape') {
|
32 |
+
onClose()
|
33 |
+
}
|
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',
|
51 |
+
md: 'max-w-md',
|
52 |
+
lg: 'max-w-lg',
|
53 |
+
xl: 'max-w-xl',
|
54 |
+
'2xl': 'max-w-2xl',
|
55 |
+
'3xl': 'max-w-3xl',
|
56 |
+
'4xl': 'max-w-4xl',
|
57 |
+
'5xl': 'max-w-5xl',
|
58 |
+
'6xl': 'max-w-6xl',
|
59 |
+
'7xl': 'max-w-7xl'
|
60 |
+
}
|
61 |
+
|
62 |
+
return (
|
63 |
+
<div className="fixed inset-0 z-50 overflow-y-auto">
|
64 |
+
{/* Backdrop */}
|
65 |
+
<div
|
66 |
+
className="fixed inset-0 bg-black bg-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-none 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" />
|
85 |
+
</button>
|
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>
|
93 |
+
</div>
|
94 |
+
</div>
|
95 |
+
)
|
96 |
+
}
|
97 |
+
|
98 |
+
export default Modal
|
src/components/ModelInfo.tsx
CHANGED
@@ -28,10 +28,10 @@ const ModelInfo = () => {
|
|
28 |
const { models, modelInfo, selectedQuantization, isFetching } = useModel()
|
29 |
|
30 |
const ModelInfoSkeleton = () => (
|
31 |
-
<div className="
|
32 |
<div className="flex items-center space-x-2">
|
33 |
<Bot className="w-4 h-4 text-blue-300" />
|
34 |
-
<div className="h-4 bg-gray-300 rounded
|
35 |
<div className="w-4 h-4 bg-gray-300 rounded-full"></div>
|
36 |
</div>
|
37 |
|
@@ -39,7 +39,7 @@ const ModelInfo = () => {
|
|
39 |
<div className="h-3 bg-gray-200 rounded w-32"></div>
|
40 |
</div>
|
41 |
|
42 |
-
<div className="
|
43 |
<div className="flex items-center space-x-1">
|
44 |
<Heart className="w-3 h-3 text-red-300" />
|
45 |
<div className="h-3 bg-gray-200 rounded w-8"></div>
|
@@ -67,63 +67,57 @@ const ModelInfo = () => {
|
|
67 |
}
|
68 |
|
69 |
return (
|
70 |
-
<div className="
|
71 |
{/* Model Name Row */}
|
72 |
-
<div className="flex items-center space-x-2">
|
73 |
-
<Bot className="w-4 h-4 text-blue-600" />
|
74 |
-
<a
|
75 |
-
href={`https://huggingface.co/${modelInfo.name}`}
|
76 |
-
target="_blank"
|
77 |
-
rel="noopener noreferrer"
|
78 |
-
className="text-sm font-medium text-gray-700 truncate max-w-80 hover:underline"
|
79 |
-
title={modelInfo.name}
|
80 |
-
>
|
81 |
-
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
82 |
-
{modelInfo.name}
|
83 |
-
</a>
|
84 |
{/* Compatibility Status */}
|
85 |
{typeof modelInfo.isCompatible === 'boolean' && (
|
86 |
-
<div className="flex
|
87 |
{modelInfo.isCompatible ? (
|
88 |
-
|
89 |
-
<CheckCircle className="w-4 h-4 text-green-500" />
|
90 |
-
</>
|
91 |
) : (
|
92 |
-
|
93 |
-
<XCircle className="w-4 h-4 text-red-500" />
|
94 |
-
</>
|
95 |
)}
|
96 |
</div>
|
97 |
)}
|
98 |
-
|
99 |
-
|
100 |
-
{/* Base Model Link */}
|
101 |
-
{modelInfo.baseId && modelInfo.baseId !== modelInfo.id && (
|
102 |
-
<div className="flex items-center space-x-2 ml-6 text-xs text-gray-600 truncate max-w-100">
|
103 |
<a
|
104 |
-
href={`https://huggingface.co/${modelInfo.
|
105 |
target="_blank"
|
106 |
rel="noopener noreferrer"
|
107 |
-
className=" hover:underline"
|
108 |
-
title={
|
109 |
>
|
110 |
-
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
111 |
-
{modelInfo.
|
112 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
</div>
|
114 |
-
|
115 |
|
116 |
-
{/* Stats
|
117 |
-
<div className="
|
118 |
{modelInfo.likes > 0 && (
|
119 |
-
<div className="flex items-center space-x-1
|
120 |
<Heart className="w-3 h-3 text-red-500" />
|
121 |
<span>{formatNumber(modelInfo.likes)}</span>
|
122 |
</div>
|
123 |
)}
|
124 |
|
125 |
{modelInfo.downloads > 0 && (
|
126 |
-
<div className="flex items-center space-x-1
|
127 |
<Download className="w-3 h-3 text-green-500" />
|
128 |
<span>{formatNumber(modelInfo.downloads)}</span>
|
129 |
</div>
|
@@ -163,8 +157,8 @@ const ModelInfo = () => {
|
|
163 |
|
164 |
{/* Incompatibility Message */}
|
165 |
{modelInfo.isCompatible === false && modelInfo.incompatibilityReason && (
|
166 |
-
<div className="bg-red-50 border border-red-200 rounded-md px-
|
167 |
-
<p className="text-
|
168 |
{modelInfo.incompatibilityReason}
|
169 |
</p>
|
170 |
</div>
|
|
|
28 |
const { models, modelInfo, selectedQuantization, isFetching } = useModel()
|
29 |
|
30 |
const ModelInfoSkeleton = () => (
|
31 |
+
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 px-3 py-3 rounded-lg border border-blue-200 space-y-3 animate-pulse w-4/5">
|
32 |
<div className="flex items-center space-x-2">
|
33 |
<Bot className="w-4 h-4 text-blue-300" />
|
34 |
+
<div className="h-4 bg-gray-300 rounded flex-1"></div>
|
35 |
<div className="w-4 h-4 bg-gray-300 rounded-full"></div>
|
36 |
</div>
|
37 |
|
|
|
39 |
<div className="h-3 bg-gray-200 rounded w-32"></div>
|
40 |
</div>
|
41 |
|
42 |
+
<div className="grid grid-cols-2 gap-2 text-xs">
|
43 |
<div className="flex items-center space-x-1">
|
44 |
<Heart className="w-3 h-3 text-red-300" />
|
45 |
<div className="h-3 bg-gray-200 rounded w-8"></div>
|
|
|
67 |
}
|
68 |
|
69 |
return (
|
70 |
+
<div className="relative bg-gradient-to-r from-blue-50 to-indigo-50 px-3 py-3 rounded-lg border border-blue-200 space-y-3 h-full w-4/5">
|
71 |
{/* Model Name Row */}
|
72 |
+
<div className="flex justify-center items-center space-x-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
{/* Compatibility Status */}
|
74 |
{typeof modelInfo.isCompatible === 'boolean' && (
|
75 |
+
<div className="flex-shrink-0 ">
|
76 |
{modelInfo.isCompatible ? (
|
77 |
+
<CheckCircle className="w-4 h-4 text-green-500" />
|
|
|
|
|
78 |
) : (
|
79 |
+
<XCircle className="w-4 h-4 text-red-500" />
|
|
|
|
|
80 |
)}
|
81 |
</div>
|
82 |
)}
|
83 |
+
<div className="flex-1 min-w-0">
|
|
|
|
|
|
|
|
|
84 |
<a
|
85 |
+
href={`https://huggingface.co/${modelInfo.name}`}
|
86 |
target="_blank"
|
87 |
rel="noopener noreferrer"
|
88 |
+
className="text-sm font-medium text-gray-700 hover:underline block truncate"
|
89 |
+
title={modelInfo.name}
|
90 |
>
|
91 |
+
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
92 |
+
{modelInfo.name}
|
93 |
</a>
|
94 |
+
{/* Base Model Link */}
|
95 |
+
{modelInfo.baseId && modelInfo.baseId !== modelInfo.id && (
|
96 |
+
<a
|
97 |
+
href={`https://huggingface.co/${modelInfo.baseId}`}
|
98 |
+
target="_blank"
|
99 |
+
rel="noopener noreferrer"
|
100 |
+
className="text-xs text-gray-500 hover:underline block truncate mt-1"
|
101 |
+
title={`Base model: ${modelInfo.baseId}`}
|
102 |
+
>
|
103 |
+
<ExternalLink className="w-3 h-3 inline-block mr-1" />(
|
104 |
+
{modelInfo.baseId})
|
105 |
+
</a>
|
106 |
+
)}
|
107 |
</div>
|
108 |
+
</div>
|
109 |
|
110 |
+
{/* Stats Grid */}
|
111 |
+
<div className="grid grid-cols-2 gap-2 text-xs text-gray-600">
|
112 |
{modelInfo.likes > 0 && (
|
113 |
+
<div className="flex items-center space-x-1">
|
114 |
<Heart className="w-3 h-3 text-red-500" />
|
115 |
<span>{formatNumber(modelInfo.likes)}</span>
|
116 |
</div>
|
117 |
)}
|
118 |
|
119 |
{modelInfo.downloads > 0 && (
|
120 |
+
<div className="flex items-center space-x-1">
|
121 |
<Download className="w-3 h-3 text-green-500" />
|
122 |
<span>{formatNumber(modelInfo.downloads)}</span>
|
123 |
</div>
|
|
|
157 |
|
158 |
{/* Incompatibility Message */}
|
159 |
{modelInfo.isCompatible === false && modelInfo.incompatibilityReason && (
|
160 |
+
<div className="bg-red-50 border border-red-200 rounded-md px-2 py-2">
|
161 |
+
<p className="text-xs text-red-700 whitespace-break-spaces">
|
162 |
{modelInfo.incompatibilityReason}
|
163 |
</p>
|
164 |
</div>
|
src/components/ModelLoader.tsx
CHANGED
@@ -46,8 +46,6 @@ const ModelLoader = () => {
|
|
46 |
setHasBeenLoaded(false)
|
47 |
}, [modelInfo, setSelectedQuantization, setHasBeenLoaded])
|
48 |
|
49 |
-
|
50 |
-
|
51 |
useEffect(() => {
|
52 |
if (!modelInfo) return
|
53 |
|
@@ -61,7 +59,6 @@ const ModelLoader = () => {
|
|
61 |
setActiveWorker(newWorker)
|
62 |
}
|
63 |
|
64 |
-
|
65 |
const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
|
66 |
const { status, output } = e.data
|
67 |
if (status === 'ready') {
|
@@ -81,7 +78,7 @@ const ModelLoader = () => {
|
|
81 |
setStatus('output')
|
82 |
const result = e.data.output!
|
83 |
setResults((prev: any[]) => [...prev, result])
|
84 |
-
|
85 |
// console.log(result)
|
86 |
} else if (status === 'error') {
|
87 |
setStatus('error')
|
@@ -130,9 +127,7 @@ const ModelLoader = () => {
|
|
130 |
<div className="flex items-center space-x-2">
|
131 |
{modelInfo.supportedQuantizations.length > 1 ? (
|
132 |
<>
|
133 |
-
<span className="text-xs text-gray-600 font-medium">
|
134 |
-
Quantization:
|
135 |
-
</span>
|
136 |
|
137 |
<div className="relative">
|
138 |
<select
|
|
|
46 |
setHasBeenLoaded(false)
|
47 |
}, [modelInfo, setSelectedQuantization, setHasBeenLoaded])
|
48 |
|
|
|
|
|
49 |
useEffect(() => {
|
50 |
if (!modelInfo) return
|
51 |
|
|
|
59 |
setActiveWorker(newWorker)
|
60 |
}
|
61 |
|
|
|
62 |
const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
|
63 |
const { status, output } = e.data
|
64 |
if (status === 'ready') {
|
|
|
78 |
setStatus('output')
|
79 |
const result = e.data.output!
|
80 |
setResults((prev: any[]) => [...prev, result])
|
81 |
+
|
82 |
// console.log(result)
|
83 |
} else if (status === 'error') {
|
84 |
setStatus('error')
|
|
|
127 |
<div className="flex items-center space-x-2">
|
128 |
{modelInfo.supportedQuantizations.length > 1 ? (
|
129 |
<>
|
130 |
+
<span className="text-xs text-gray-600 font-medium">Quant:</span>
|
|
|
|
|
131 |
|
132 |
<div className="relative">
|
133 |
<select
|
src/components/ModelReadme.tsx
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
import {
|
2 |
-
import {
|
|
|
3 |
import MarkdownRenderer from './MarkdownRenderer'
|
4 |
|
5 |
interface ModelReadmeProps {
|
@@ -8,31 +9,47 @@ interface ModelReadmeProps {
|
|
8 |
modelName: string
|
9 |
}
|
10 |
|
11 |
-
const ModelReadme = ({ readme, pipeline, modelName}: ModelReadmeProps) => {
|
|
|
12 |
|
13 |
-
|
14 |
-
<div className="
|
15 |
-
<
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
</div>
|
27 |
-
</DisclosureButton>
|
28 |
-
<DisclosurePanel className="px-4 py-5 bg-gray-50 rounded-b-lg border-l border-r border-b border-gray-200 max-h-[600px] overflow-y-auto">
|
29 |
-
<div className="text-sm text-gray-800">
|
30 |
-
<MarkdownRenderer content={readme} />
|
31 |
-
</div>
|
32 |
-
</DisclosurePanel>
|
33 |
-
</Disclosure>
|
34 |
</div>
|
35 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
}
|
37 |
|
38 |
export default ModelReadme
|
|
|
1 |
+
import { ExternalLink, FileText } from 'lucide-react'
|
2 |
+
import { useState } from 'react'
|
3 |
+
import Modal from './Modal'
|
4 |
import MarkdownRenderer from './MarkdownRenderer'
|
5 |
|
6 |
interface ModelReadmeProps {
|
|
|
9 |
modelName: string
|
10 |
}
|
11 |
|
12 |
+
const ModelReadme = ({ readme, pipeline, modelName }: ModelReadmeProps) => {
|
13 |
+
const [isModalOpen, setIsModalOpen] = useState(false)
|
14 |
|
15 |
+
const title = (
|
16 |
+
<div className="flex items-center space-x-2">
|
17 |
+
<a
|
18 |
+
className="truncate hover:underline"
|
19 |
+
href={`https://huggingface.co/${modelName}`}
|
20 |
+
target="_blank"
|
21 |
+
rel="noopener noreferrer"
|
22 |
+
>
|
23 |
+
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
24 |
+
|
25 |
+
{modelName}
|
26 |
+
</a>
|
27 |
+
<span className=" text-gray-500">README.md</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
</div>
|
29 |
)
|
30 |
+
|
31 |
+
return (
|
32 |
+
<>
|
33 |
+
<button
|
34 |
+
onClick={() => setIsModalOpen(true)}
|
35 |
+
className="flex items-center w-full px-3 py-2 text-sm text-gray-600 bg-gray-50 rounded-lg border border-gray-200 hover:bg-gray-100 transition-colors"
|
36 |
+
>
|
37 |
+
<FileText className="w-4 h-4 mr-2 flex-shrink-0" />
|
38 |
+
<span className="truncate">View README.md</span>
|
39 |
+
</button>
|
40 |
+
|
41 |
+
<Modal
|
42 |
+
isOpen={isModalOpen}
|
43 |
+
onClose={() => setIsModalOpen(false)}
|
44 |
+
title={title}
|
45 |
+
maxWidth="5xl"
|
46 |
+
>
|
47 |
+
<div className="text-sm max-w-none px-4">
|
48 |
+
<MarkdownRenderer content={readme} />
|
49 |
+
</div>
|
50 |
+
</Modal>
|
51 |
+
</>
|
52 |
+
)
|
53 |
}
|
54 |
|
55 |
export default ModelReadme
|
src/components/ModelSelector.tsx
CHANGED
@@ -19,6 +19,7 @@ import {
|
|
19 |
Search,
|
20 |
X
|
21 |
} from 'lucide-react'
|
|
|
22 |
|
23 |
type SortOption = 'likes' | 'downloads' | 'createdAt' | 'name'
|
24 |
|
@@ -110,7 +111,8 @@ function ModelSelector() {
|
|
110 |
readme: modelInfoResponse.readme,
|
111 |
hasChatTemplate: Boolean(
|
112 |
modelInfoResponse.config?.tokenizer_config?.chat_template
|
113 |
-
)
|
|
|
114 |
}
|
115 |
setModelInfo(modelInfo)
|
116 |
setIsCustomModel(isCustom)
|
@@ -280,9 +282,11 @@ function ModelSelector() {
|
|
280 |
<ListboxButton className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left flex items-center justify-between">
|
281 |
<div className="flex items-center justify-between w-full">
|
282 |
<div className="flex flex-col flex-1 min-w-0">
|
283 |
-
<
|
284 |
-
|
285 |
-
|
|
|
|
|
286 |
</div>
|
287 |
|
288 |
<div className="flex items-center space-x-3">
|
@@ -449,45 +453,49 @@ function ModelSelector() {
|
|
449 |
key={model.id}
|
450 |
value={model}
|
451 |
className={({ active, selected }) =>
|
452 |
-
`px-3 py-
|
453 |
active ? 'bg-gray-50' : ''
|
454 |
} ${selected ? 'bg-blue-50' : ''}`
|
455 |
}
|
456 |
>
|
457 |
{({ selected }) => (
|
458 |
-
<div className="relative flex items-
|
459 |
-
<div className="flex
|
460 |
-
<
|
461 |
-
{model.id}
|
462 |
-
|
463 |
-
|
464 |
-
{selected && (
|
465 |
-
<Check className="mr-2 w-4 h-4 text-blue-600 ml-2 flex-shrink-0" />
|
466 |
-
)}
|
467 |
-
{/* Stats Display */}
|
468 |
-
{hasStats && (
|
469 |
-
<div className="flex items-center space-x-3 text-xs text-gray-500 flex-shrink-0">
|
470 |
-
{model.likes > 0 && (
|
471 |
-
<div className="flex items-center space-x-1">
|
472 |
-
<Heart className="w-3 h-3 text-red-500" />
|
473 |
-
<span>{formatNumber(model.likes)}</span>
|
474 |
-
</div>
|
475 |
-
)}
|
476 |
-
|
477 |
-
{model.downloads > 0 && (
|
478 |
-
<div className="flex items-center space-x-1">
|
479 |
-
<Download className="w-3 h-3 text-green-500" />
|
480 |
-
<span>{formatNumber(model.downloads)}</span>
|
481 |
-
</div>
|
482 |
-
)}
|
483 |
-
|
484 |
-
{model.createdAt && (
|
485 |
-
<span className="text-xs text-gray-400">
|
486 |
-
{model.createdAt.split('T')[0]}
|
487 |
</span>
|
|
|
|
|
|
|
488 |
)}
|
489 |
</div>
|
490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
491 |
</div>
|
492 |
)}
|
493 |
</ListboxOption>
|
|
|
19 |
Search,
|
20 |
X
|
21 |
} from 'lucide-react'
|
22 |
+
import Tooltip from './Tooltip'
|
23 |
|
24 |
type SortOption = 'likes' | 'downloads' | 'createdAt' | 'name'
|
25 |
|
|
|
111 |
readme: modelInfoResponse.readme,
|
112 |
hasChatTemplate: Boolean(
|
113 |
modelInfoResponse.config?.tokenizer_config?.chat_template
|
114 |
+
),
|
115 |
+
widgetData: modelInfoResponse.widgetData
|
116 |
}
|
117 |
setModelInfo(modelInfo)
|
118 |
setIsCustomModel(isCustom)
|
|
|
282 |
<ListboxButton className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left flex items-center justify-between">
|
283 |
<div className="flex items-center justify-between w-full">
|
284 |
<div className="flex flex-col flex-1 min-w-0">
|
285 |
+
<Tooltip content={modelInfo?.id || 'Select a model'}>
|
286 |
+
<span className="truncate font-medium block">
|
287 |
+
{modelInfo?.id || 'Select a model'}
|
288 |
+
</span>
|
289 |
+
</Tooltip>
|
290 |
</div>
|
291 |
|
292 |
<div className="flex items-center space-x-3">
|
|
|
453 |
key={model.id}
|
454 |
value={model}
|
455 |
className={({ active, selected }) =>
|
456 |
+
`px-3 py-3 cursor-pointer border-b border-gray-100 last:border-b-0 ${
|
457 |
active ? 'bg-gray-50' : ''
|
458 |
} ${selected ? 'bg-blue-50' : ''}`
|
459 |
}
|
460 |
>
|
461 |
{({ selected }) => (
|
462 |
+
<div className="relative flex items-start py-1">
|
463 |
+
<div className="flex-1 min-w-0 pr-3">
|
464 |
+
<div className="flex items-center justify-between">
|
465 |
+
<Tooltip content={model.id}>
|
466 |
+
<span className="text-sm font-medium truncate block max-w-full">
|
467 |
+
{model.id}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
</span>
|
469 |
+
</Tooltip>
|
470 |
+
{selected && (
|
471 |
+
<Check className="w-4 h-4 text-blue-600 ml-2 flex-shrink-0" />
|
472 |
)}
|
473 |
</div>
|
474 |
+
{/* Stats Display */}
|
475 |
+
{hasStats && (
|
476 |
+
<div className="flex items-center space-x-3 text-xs text-gray-500 mt-1">
|
477 |
+
{model.likes > 0 && (
|
478 |
+
<div className="flex items-center space-x-1">
|
479 |
+
<Heart className="w-3 h-3 text-red-500" />
|
480 |
+
<span>{formatNumber(model.likes)}</span>
|
481 |
+
</div>
|
482 |
+
)}
|
483 |
+
{model.downloads > 0 && (
|
484 |
+
<div className="flex items-center space-x-1">
|
485 |
+
<Download className="w-3 h-3 text-green-500" />
|
486 |
+
<span>
|
487 |
+
{formatNumber(model.downloads)}
|
488 |
+
</span>
|
489 |
+
</div>
|
490 |
+
)}
|
491 |
+
{model.createdAt && (
|
492 |
+
<span className="text-xs text-gray-400">
|
493 |
+
{model.createdAt.split('T')[0]}
|
494 |
+
</span>
|
495 |
+
)}
|
496 |
+
</div>
|
497 |
+
)}
|
498 |
+
</div>
|
499 |
</div>
|
500 |
)}
|
501 |
</ListboxOption>
|
src/components/Sidebar.tsx
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { X } from 'lucide-react'
|
2 |
+
import PipelineSelector from './PipelineSelector'
|
3 |
+
import ModelSelector from './ModelSelector'
|
4 |
+
import ModelInfo from './ModelInfo'
|
5 |
+
import { useModel } from '../contexts/ModelContext'
|
6 |
+
|
7 |
+
interface SidebarProps {
|
8 |
+
isOpen: boolean
|
9 |
+
onClose: () => void
|
10 |
+
}
|
11 |
+
|
12 |
+
const Sidebar = ({ isOpen, onClose }: SidebarProps) => {
|
13 |
+
const { pipeline, setPipeline } = useModel()
|
14 |
+
|
15 |
+
return (
|
16 |
+
<>
|
17 |
+
{/* Overlay */}
|
18 |
+
{isOpen && (
|
19 |
+
<div
|
20 |
+
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
|
21 |
+
onClick={onClose}
|
22 |
+
/>
|
23 |
+
)}
|
24 |
+
|
25 |
+
{/* Sidebar */}
|
26 |
+
<div
|
27 |
+
className={`
|
28 |
+
fixed top-0 right-0 h-full w-full sm:w-[600px] lg:w-1/5 2xl:w-2/5 min-w-[400px] max-w-[500px] bg-white shadow-xl z-40 transform transition-transform duration-300 ease-in-out
|
29 |
+
${isOpen ? 'translate-x-0' : 'translate-x-full'}
|
30 |
+
lg:translate-x-0 lg:static lg:shadow-none lg:border-l lg:border-gray-200
|
31 |
+
`}
|
32 |
+
>
|
33 |
+
<div className="flex flex-col h-full">
|
34 |
+
{/* Header */}
|
35 |
+
<div className="flex items-center justify-between p-4 border-b border-gray-200 lg:hidden">
|
36 |
+
<h2 className="text-lg font-semibold text-gray-900">
|
37 |
+
Configuration
|
38 |
+
</h2>
|
39 |
+
<button
|
40 |
+
onClick={onClose}
|
41 |
+
className="p-2 text-gray-400 hover:text-gray-600 rounded-lg hover:bg-gray-100"
|
42 |
+
>
|
43 |
+
<X className="w-5 h-5" />
|
44 |
+
</button>
|
45 |
+
</div>
|
46 |
+
|
47 |
+
{/* Content */}
|
48 |
+
<div className="flex-1 overflow-y-auto p-4 space-y-6">
|
49 |
+
{/* Pipeline Selection */}
|
50 |
+
<div className="space-y-3 flex flex-row items-center space-x-4 text-center">
|
51 |
+
<h3 className="text-lg font-semibold text-gray-900 w-2/5 mt-2">
|
52 |
+
Choose a Pipeline
|
53 |
+
</h3>
|
54 |
+
<div className="w-3/5">
|
55 |
+
<PipelineSelector
|
56 |
+
pipeline={pipeline}
|
57 |
+
setPipeline={setPipeline}
|
58 |
+
/>
|
59 |
+
</div>
|
60 |
+
</div>
|
61 |
+
|
62 |
+
{/* Model Selection */}
|
63 |
+
<div className="space-y-3">
|
64 |
+
<h3 className="text-lg font-semibold text-gray-900">
|
65 |
+
Select Model
|
66 |
+
</h3>
|
67 |
+
<ModelSelector />
|
68 |
+
</div>
|
69 |
+
|
70 |
+
{/* Model Info */}
|
71 |
+
<div className="flex items-center justify-center">
|
72 |
+
<ModelInfo />
|
73 |
+
</div>
|
74 |
+
|
75 |
+
<hr className="border-gray-200" />
|
76 |
+
</div>
|
77 |
+
</div>
|
78 |
+
</div>
|
79 |
+
</>
|
80 |
+
)
|
81 |
+
}
|
82 |
+
|
83 |
+
export default Sidebar
|
src/components/TextClassification.tsx
CHANGED
@@ -1,7 +1,5 @@
|
|
1 |
-
import { useState, useCallback } from 'react'
|
2 |
-
import {
|
3 |
-
TextClassificationWorkerInput,
|
4 |
-
} from '../types'
|
5 |
import { useModel } from '../contexts/ModelContext'
|
6 |
|
7 |
const PLACEHOLDER_TEXTS: string[] = [
|
@@ -19,7 +17,29 @@ const PLACEHOLDER_TEXTS: string[] = [
|
|
19 |
|
20 |
function TextClassification() {
|
21 |
const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join('\n'))
|
22 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
const classify = useCallback(() => {
|
25 |
if (!modelInfo || !activeWorker) {
|
@@ -44,13 +64,17 @@ function TextClassification() {
|
|
44 |
|
45 |
return (
|
46 |
<div className="flex flex-col h-[60vh] max-h-[100vh] w-full p-4">
|
47 |
-
<h1 className="text-2xl font-bold mb-4 flex-shrink-0">
|
|
|
|
|
48 |
|
49 |
<div className="flex flex-col lg:flex-row gap-4 flex-1 min-h-0">
|
50 |
{/* Input Section */}
|
51 |
<div className="flex flex-col w-full lg:w-1/2 min-h-0">
|
52 |
-
<label className="text-lg font-medium mb-2 flex-shrink-0">
|
53 |
-
|
|
|
|
|
54 |
<div className="flex flex-col flex-1 min-h-0">
|
55 |
<textarea
|
56 |
className="border border-gray-300 rounded p-3 flex-1 resize-none min-h-[200px]"
|
@@ -65,7 +89,8 @@ function TextClassification() {
|
|
65 |
disabled={busy}
|
66 |
onClick={classify}
|
67 |
>
|
68 |
-
{hasBeenLoaded
|
|
|
69 |
? 'Classify Text'
|
70 |
: 'Processing...'
|
71 |
: 'Load model first'}
|
|
|
1 |
+
import { useState, useCallback, useEffect } from 'react'
|
2 |
+
import { TextClassificationWorkerInput } from '../types'
|
|
|
|
|
3 |
import { useModel } from '../contexts/ModelContext'
|
4 |
|
5 |
const PLACEHOLDER_TEXTS: string[] = [
|
|
|
17 |
|
18 |
function TextClassification() {
|
19 |
const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join('\n'))
|
20 |
+
const [numberExamples, setNumberExamples] = useState(PLACEHOLDER_TEXTS.length)
|
21 |
+
const {
|
22 |
+
activeWorker,
|
23 |
+
status,
|
24 |
+
modelInfo,
|
25 |
+
results,
|
26 |
+
setResults,
|
27 |
+
hasBeenLoaded,
|
28 |
+
selectedQuantization
|
29 |
+
} = useModel()
|
30 |
+
|
31 |
+
useEffect(() => {
|
32 |
+
if (modelInfo?.widgetData) {
|
33 |
+
const examples = modelInfo.widgetData.map((e: any) => e.text)
|
34 |
+
if (examples.length > 0) {
|
35 |
+
setText(examples.join('\n'))
|
36 |
+
}
|
37 |
+
}
|
38 |
+
}, [modelInfo])
|
39 |
+
|
40 |
+
useEffect(() => {
|
41 |
+
setNumberExamples(text.split('\n').length)
|
42 |
+
}, [text])
|
43 |
|
44 |
const classify = useCallback(() => {
|
45 |
if (!modelInfo || !activeWorker) {
|
|
|
64 |
|
65 |
return (
|
66 |
<div className="flex flex-col h-[60vh] max-h-[100vh] w-full p-4">
|
67 |
+
<h1 className="text-2xl font-bold mb-4 flex-shrink-0">
|
68 |
+
Text Classification
|
69 |
+
</h1>
|
70 |
|
71 |
<div className="flex flex-col lg:flex-row gap-4 flex-1 min-h-0">
|
72 |
{/* Input Section */}
|
73 |
<div className="flex flex-col w-full lg:w-1/2 min-h-0">
|
74 |
+
<label className="text-lg font-medium mb-2 flex-shrink-0">
|
75 |
+
Input Text ({numberExamples} examples):
|
76 |
+
</label>
|
77 |
+
|
78 |
<div className="flex flex-col flex-1 min-h-0">
|
79 |
<textarea
|
80 |
className="border border-gray-300 rounded p-3 flex-1 resize-none min-h-[200px]"
|
|
|
89 |
disabled={busy}
|
90 |
onClick={classify}
|
91 |
>
|
92 |
+
{hasBeenLoaded
|
93 |
+
? !busy
|
94 |
? 'Classify Text'
|
95 |
: 'Processing...'
|
96 |
: 'Load model first'}
|
src/components/Tooltip.tsx
CHANGED
@@ -11,7 +11,7 @@ const Tooltip: React.FC<TooltipProps> = ({ children, content, className }) => {
|
|
11 |
<div className="relative group flex items-center">
|
12 |
{children}
|
13 |
<div
|
14 |
-
className={`absolute
|
15 |
>
|
16 |
{content}
|
17 |
</div>
|
|
|
11 |
<div className="relative group flex items-center">
|
12 |
{children}
|
13 |
<div
|
14 |
+
className={`absolute left-0 top-full mt-2 w-max max-w-sm px-3 py-1.5 text-xs font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-opacity duration-300 z-20 pointer-events-none ${className}`}
|
15 |
>
|
16 |
{content}
|
17 |
</div>
|
src/types.ts
CHANGED
@@ -72,7 +72,7 @@ type q8 = (typeof q8Types)[number]
|
|
72 |
type q4 = (typeof q4Types)[number]
|
73 |
type fp16 = (typeof fp16Types)[number]
|
74 |
type fp32 = (typeof fp32Types)[number]
|
75 |
-
|
76 |
export type QuantizationType = q8 | q4 | fp16 | fp32
|
77 |
export const allQuantizationTypes = [
|
78 |
...q8Types,
|
@@ -95,6 +95,7 @@ export interface ModelInfo {
|
|
95 |
baseId?: string
|
96 |
readme?: string
|
97 |
hasChatTemplate: boolean
|
|
|
98 |
}
|
99 |
|
100 |
export interface ModelInfoResponse {
|
@@ -130,6 +131,7 @@ export interface ModelInfoResponse {
|
|
130 |
siblings?: {
|
131 |
rfilename: string
|
132 |
}[]
|
|
|
133 |
modelId?: string
|
134 |
isCompatible: boolean
|
135 |
incompatibilityReason?: string
|
|
|
72 |
type q4 = (typeof q4Types)[number]
|
73 |
type fp16 = (typeof fp16Types)[number]
|
74 |
type fp32 = (typeof fp32Types)[number]
|
75 |
+
|
76 |
export type QuantizationType = q8 | q4 | fp16 | fp32
|
77 |
export const allQuantizationTypes = [
|
78 |
...q8Types,
|
|
|
95 |
baseId?: string
|
96 |
readme?: string
|
97 |
hasChatTemplate: boolean
|
98 |
+
widgetData?: any
|
99 |
}
|
100 |
|
101 |
export interface ModelInfoResponse {
|
|
|
131 |
siblings?: {
|
132 |
rfilename: string
|
133 |
}[]
|
134 |
+
widgetData?: any
|
135 |
modelId?: string
|
136 |
isCompatible: boolean
|
137 |
incompatibilityReason?: string
|
tailwind.config.js
CHANGED
@@ -3,6 +3,6 @@ module.exports = {
|
|
3 |
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
4 |
theme: {
|
5 |
extend: {}
|
6 |
-
}
|
7 |
-
plugins: []
|
8 |
-
}
|
|
|
3 |
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
4 |
theme: {
|
5 |
extend: {}
|
6 |
+
}
|
7 |
+
// plugins: [require('@tailwindcss/typography')]
|
8 |
+
}
|