Vokturz commited on
Commit
f3b30b4
·
1 Parent(s): 117cfaa

update layout adding a Modal and Sidebar

Browse files
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 PipelineSelector from './components/PipelineSelector'
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
- pipeline,
16
- setPipeline,
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
- <main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
45
- <div className="mb-8">
46
- <div className="bg-white rounded-lg border p-6">
47
- <div className="flex items-center lg:items-start justify-between mx-auto flex-col lg:flex-row">
48
- <div className="space-y-2 w-10/12 lg:w-7/12">
49
- <div className="space-y-2">
50
- <span className="text-lg font-semibold text-gray-900 block">
51
- Choose a Pipeline
52
- </span>
53
- <PipelineSelector
 
 
 
 
 
 
 
 
 
 
 
 
54
  pipeline={pipeline}
55
- setPipeline={setPipeline}
56
  />
57
- </div>
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
- {modelInfo?.readme && (
73
- <ModelReadme
74
- readme={modelInfo.readme}
75
- modelName={modelInfo.name}
76
- pipeline={pipeline}
77
- />
78
- )}
79
  </div>
80
- </div>
81
-
82
- <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
83
- {pipeline === 'zero-shot-classification' && (
84
- <ZeroShotClassification />
85
- )}
86
- {pipeline === 'text-classification' && <TextClassification />}
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="mt-5 bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-3 rounded-lg border border-blue-200 space-y-4 h-full min-h-[160px] animate-pulse w-[400px]">
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 w-48"></div>
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="flex items-center justify-self-end space-x-4">
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="mt-5 bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-3 rounded-lg border border-blue-200 space-y-3 h-full min-h-[150px]">
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 items-center space-x-1">
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
- </div>
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.baseId}`}
105
  target="_blank"
106
  rel="noopener noreferrer"
107
- className=" hover:underline"
108
- title={`Base model: ${modelInfo.baseId}`}
109
  >
110
- <ExternalLink className="w-3 h-3 inline-block mr-1" />(
111
- {modelInfo.baseId})
112
  </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  </div>
114
- )}
115
 
116
- {/* Stats Row */}
117
- <div className="flex items-center justify-self-end space-x-4 text-xs text-gray-600">
118
  {modelInfo.likes > 0 && (
119
- <div className="flex items-center space-x-1 cursor-default">
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 cursor-default">
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-3 py-2">
167
- <p className="text-sm text-red-700 whitespace-break-spaces">
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 { FileText, ChevronDown } from 'lucide-react'
2
- import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
 
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
- return (
14
- <div className="mt-2">
15
- <Disclosure>
16
- <DisclosureButton className="flex justify-between items-center w-full px-4 py-3 bg-gray-50 rounded-lg border border-gray-200 hover:bg-gray-100 transition-colors">
17
- <div className="flex items-center text-sm text-gray-600">
18
- <FileText className="w-4 h-4 mr-2" />
19
- README.md
20
- </div>
21
- <div className="flex items-center space-x-2">
22
- <div className="text-xs text-gray-400">
23
- <i>{pipeline}</i> • {modelName}
24
- </div>
25
- <ChevronDown className="w-4 h-4 text-gray-400 transition-transform ui-open:rotate-180" />
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
- <span className="truncate font-medium">
284
- {modelInfo?.id || 'Select a model'}
285
- </span>
 
 
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-2 cursor-pointer border-b border-gray-100 last:border-b-0 ${
453
  active ? 'bg-gray-50' : ''
454
  } ${selected ? 'bg-blue-50' : ''}`
455
  }
456
  >
457
  {({ selected }) => (
458
- <div className="relative flex items-center justify-between">
459
- <div className="flex items-center flex-1 w-5/12">
460
- <span className="text-sm font-medium truncate w-11/12 ">
461
- {model.id}
462
- </span>
463
- </div>
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 { activeWorker, status, modelInfo, results, setResults, hasBeenLoaded, selectedQuantization} = useModel()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">Text Classification</h1>
 
 
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">Input Text:</label>
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 ? !busy
 
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 bottom-full mb-2 w-max max-w-xs 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-10 ${className}`}
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
+ }