Vokturz commited on
Commit
852dc0d
·
1 Parent(s): f3b30b4

Move text generation config to sidebar with context

Browse files
src/App.tsx CHANGED
@@ -8,6 +8,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)
@@ -34,50 +35,51 @@ function App() {
34
  return (
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
  }
 
8
  import TextGeneration from './components/TextGeneration'
9
  import Sidebar from './components/Sidebar'
10
  import ModelReadme from './components/ModelReadme'
11
+ import { PipelineLayout } from './components/PipelineLayout'
12
 
13
  function App() {
14
  const [isSidebarOpen, setIsSidebarOpen] = useState(false)
 
35
  return (
36
  <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
37
  <Header />
38
+ <PipelineLayout>
39
+ <div className="flex h-[calc(100vh-4rem)]">
40
+ {/* Header is h-16 = 4rem */}
41
+ {/* Main Content */}
42
+ <main className="flex-1 overflow-auto">
43
+ <div className="h-full px-4 sm:px-6 lg:px-8 py-8 lg:pr-4 max-w-none">
44
+ {/* Mobile menu button */}
45
+ <div className="flex flex-row space-x-4">
46
+ <div className="lg:hidden mb-4">
47
+ <button
48
+ onClick={() => setIsSidebarOpen(true)}
49
+ 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"
50
+ >
51
+ <Settings className="w-4 h-4 mr-2" />
52
+ Configuration
53
+ </button>
54
+ </div>
55
+ {/* Model README Button */}
56
+ <div className="mb-4">
57
+ {modelInfo?.readme && (
58
+ <ModelReadme
59
+ readme={modelInfo.readme}
60
+ modelName={modelInfo.name}
61
+ pipeline={pipeline}
62
+ />
63
+ )}
64
+ </div>
65
  </div>
66
+ {/* Pipeline Component */}
67
+ <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
68
+ {pipeline === 'zero-shot-classification' && (
69
+ <ZeroShotClassification />
 
 
 
 
70
  )}
71
+ {pipeline === 'text-classification' && <TextClassification />}
72
+ {pipeline === 'text-generation' && <TextGeneration />}
73
  </div>
74
  </div>
75
+ </main>
76
+ {/* Sidebar */}
77
+ <Sidebar
78
+ isOpen={isSidebarOpen}
79
+ onClose={() => setIsSidebarOpen(false)}
80
+ />
81
+ </div>
82
+ </PipelineLayout>
 
 
 
 
 
 
 
 
83
  </div>
84
  )
85
  }
src/components/PipelineLayout.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useModel } from '../contexts/ModelContext'
2
+ import { TextGenerationProvider } from '../contexts/TextGenerationContext'
3
+
4
+ export const PipelineLayout = ({ children }: { children: React.ReactNode }) => {
5
+ const { pipeline } = useModel()
6
+
7
+ switch (pipeline) {
8
+ case 'text-generation':
9
+ return <TextGenerationProvider>{children}</TextGenerationProvider>
10
+
11
+ // case 'zero-shot-classification':
12
+ // return <ZeroShotProvider>{children}</ZeroShotProvider>;
13
+
14
+ default:
15
+ return <>{children}</>
16
+ }
17
+ }
src/components/Sidebar.tsx CHANGED
@@ -3,6 +3,7 @@ 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
@@ -73,6 +74,7 @@ const Sidebar = ({ isOpen, onClose }: SidebarProps) => {
73
  </div>
74
 
75
  <hr className="border-gray-200" />
 
76
  </div>
77
  </div>
78
  </div>
 
3
  import ModelSelector from './ModelSelector'
4
  import ModelInfo from './ModelInfo'
5
  import { useModel } from '../contexts/ModelContext'
6
+ import TextGenerationConfig from './TextGenerationConfig'
7
 
8
  interface SidebarProps {
9
  isOpen: boolean
 
74
  </div>
75
 
76
  <hr className="border-gray-200" />
77
+ {pipeline === 'text-generation' && <TextGenerationConfig />}
78
  </div>
79
  </div>
80
  </div>
src/components/TextGeneration.tsx CHANGED
@@ -1,36 +1,24 @@
1
  import { useState, useRef, useEffect, useCallback } from 'react'
2
- import { Send, Settings, Trash2, Loader2, X } from 'lucide-react'
3
- import { Dialog, Transition, Switch } from '@headlessui/react'
4
- import { Fragment } from 'react'
5
- import {
6
- ChatMessage,
7
- TextGenerationWorkerInput,
8
- WorkerMessage
9
- } from '../types'
10
  import { useModel } from '../contexts/ModelContext'
 
11
 
12
  function TextGeneration() {
13
- const [messages, setMessages] = useState<ChatMessage[]>([
14
- { role: 'system', content: 'You are a helpful assistant.' }
15
- ])
16
  const [currentMessage, setCurrentMessage] = useState<string>('')
17
- const [showSettings, setShowSettings] = useState<boolean>(false)
18
-
19
- // For simple text generation
20
  const [prompt, setPrompt] = useState<string>('')
21
  const [generatedText, setGeneratedText] = useState<string>('')
22
-
23
- // Generation parameters
24
- const [temperature, setTemperature] = useState<number>(0.7)
25
- const [maxTokens, setMaxTokens] = useState<number>(100)
26
- const [topP, setTopP] = useState<number>(0.9)
27
- const [topK, setTopK] = useState<number>(50)
28
- const [doSample, setDoSample] = useState<boolean>(true)
29
-
30
- // Generation state
31
  const [isGenerating, setIsGenerating] = useState<boolean>(false)
32
 
33
- const { activeWorker, status, modelInfo, hasBeenLoaded, selectedQuantization } = useModel()
 
 
 
 
 
 
34
  const messagesEndRef = useRef<HTMLDivElement>(null)
35
 
36
  const scrollToBottom = () => {
@@ -49,15 +37,13 @@ function TextGeneration() {
49
  }, [activeWorker, isGenerating])
50
 
51
  const handleSendMessage = useCallback(() => {
52
- if (!currentMessage.trim() || !modelInfo || !activeWorker || isGenerating) {
53
  return
54
- }
55
 
56
  const userMessage: ChatMessage = {
57
  role: 'user',
58
  content: currentMessage.trim()
59
  }
60
-
61
  const updatedMessages = [...messages, userMessage]
62
  setMessages(updatedMessages)
63
  setCurrentMessage('')
@@ -68,22 +54,28 @@ function TextGeneration() {
68
  messages: updatedMessages,
69
  hasChatTemplate: modelInfo.hasChatTemplate,
70
  model: modelInfo.id,
71
- temperature,
72
- max_new_tokens: maxTokens,
73
- top_p: topP,
74
- top_k: topK,
75
- do_sample: doSample,
76
  dtype: selectedQuantization ?? 'fp32'
77
  }
78
 
79
  activeWorker.postMessage(message)
80
- }, [currentMessage, messages, modelInfo, activeWorker, temperature, maxTokens, topP, topK, doSample, isGenerating, selectedQuantization])
 
 
 
 
 
 
 
 
 
81
 
82
  const handleGenerateText = useCallback(() => {
83
- if (!prompt.trim() || !modelInfo || !activeWorker || isGenerating) {
84
- return
85
- }
86
-
87
  setIsGenerating(true)
88
 
89
  const message: TextGenerationWorkerInput = {
@@ -91,53 +83,51 @@ function TextGeneration() {
91
  prompt: prompt.trim(),
92
  hasChatTemplate: modelInfo.hasChatTemplate,
93
  model: modelInfo.id,
94
- temperature,
95
- max_new_tokens: maxTokens,
96
- top_p: topP,
97
- top_k: topK,
98
- do_sample: doSample,
99
  dtype: selectedQuantization ?? 'fp32'
100
  }
101
 
102
  activeWorker.postMessage(message)
103
- }, [prompt, modelInfo, activeWorker, temperature, maxTokens, topP, topK, doSample, isGenerating, selectedQuantization])
 
 
 
 
 
 
 
104
 
105
  useEffect(() => {
106
  if (!activeWorker) return
107
-
108
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
109
  const { status, output } = e.data
110
-
111
  if (status === 'output' && output) {
112
  setIsGenerating(false)
113
  if (modelInfo?.hasChatTemplate) {
114
- // Chat mode
115
  const assistantMessage: ChatMessage = {
116
  role: 'assistant',
117
  content: output.content
118
  }
119
- setMessages(prev => [...prev, assistantMessage])
120
  } else {
121
- // Simple text generation mode
122
  setGeneratedText(output.content)
123
  }
124
- } else if (status === 'ready') {
125
- setIsGenerating(false)
126
- } else if (status === 'error') {
127
  setIsGenerating(false)
128
  }
129
  }
130
-
131
  activeWorker.addEventListener('message', onMessageReceived)
132
-
133
- return () => {
134
- activeWorker.removeEventListener('message', onMessageReceived)
135
- }
136
- }, [activeWorker, modelInfo?.hasChatTemplate])
137
 
138
  const handleKeyPress = (e: React.KeyboardEvent) => {
139
  if (e.key === 'Enter' && !e.shiftKey) {
140
  e.preventDefault()
 
141
  if (modelInfo?.hasChatTemplate) {
142
  handleSendMessage()
143
  } else {
@@ -148,21 +138,13 @@ function TextGeneration() {
148
 
149
  const clearChat = () => {
150
  if (modelInfo?.hasChatTemplate) {
151
- setMessages([{ role: 'system', content: 'You are a helpful assistant.' }])
152
  } else {
153
  setPrompt('')
154
  setGeneratedText('')
155
  }
156
  }
157
 
158
- const updateSystemMessage = (content: string) => {
159
- setMessages(prev => [
160
- { role: 'system', content },
161
- ...prev.filter(msg => msg.role !== 'system')
162
- ])
163
- }
164
-
165
-
166
  const busy = status !== 'ready' || isGenerating
167
  const hasChatTemplate = modelInfo?.hasChatTemplate
168
 
@@ -173,17 +155,10 @@ function TextGeneration() {
173
  {hasChatTemplate ? 'Chat with AI' : 'Text Generation'}
174
  </h1>
175
  <div className="flex gap-2">
176
- <button
177
- onClick={() => setShowSettings(true)}
178
- className="p-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
179
- title="Settings"
180
- >
181
- <Settings className="w-4 h-4" />
182
- </button>
183
  <button
184
  onClick={clearChat}
185
  className="p-2 bg-red-100 hover:bg-red-200 rounded-lg transition-colors"
186
- title={hasChatTemplate ? "Clear Chat" : "Clear Text"}
187
  >
188
  <Trash2 className="w-4 h-4" />
189
  </button>
@@ -199,188 +174,39 @@ function TextGeneration() {
199
  </div>
200
  </div>
201
 
202
- {/* Settings Dialog using Headless UI */}
203
- <Transition appear show={showSettings} as={Fragment}>
204
- <Dialog as="div" className="relative z-10" onClose={() => setShowSettings(false)}>
205
- <Transition.Child
206
- as={Fragment}
207
- enter="ease-out duration-300"
208
- enterFrom="opacity-0"
209
- enterTo="opacity-100"
210
- leave="ease-in duration-200"
211
- leaveFrom="opacity-100"
212
- leaveTo="opacity-0"
213
- >
214
- <div className="fixed inset-0 bg-black bg-opacity-25" />
215
- </Transition.Child>
216
-
217
- <div className="fixed inset-0 overflow-y-auto">
218
- <div className="flex min-h-full items-center justify-center p-4 text-center">
219
- <Transition.Child
220
- as={Fragment}
221
- enter="ease-out duration-300"
222
- enterFrom="opacity-0 scale-95"
223
- enterTo="opacity-100 scale-100"
224
- leave="ease-in duration-200"
225
- leaveFrom="opacity-100 scale-100"
226
- leaveTo="opacity-0 scale-95"
227
- >
228
- <Dialog.Panel className="w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
229
- <Dialog.Title
230
- as="h3"
231
- className="text-lg font-medium leading-6 text-gray-900 mb-4"
232
- >
233
- Generation Settings
234
- </Dialog.Title>
235
-
236
- <div className="space-y-6">
237
- {/* Generation Parameters */}
238
- <div>
239
- <h4 className="font-semibold text-gray-800 mb-3">Parameters</h4>
240
- <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
241
- <div>
242
- <label className="block text-sm font-medium text-gray-700 mb-1">
243
- Temperature: {temperature}
244
- </label>
245
- <input
246
- type="range"
247
- min="0.1"
248
- max="2.0"
249
- step="0.1"
250
- value={temperature}
251
- onChange={(e) => setTemperature(parseFloat(e.target.value))}
252
- className="w-full"
253
- />
254
- </div>
255
-
256
- <div>
257
- <label className="block text-sm font-medium text-gray-700 mb-1">
258
- Max Tokens: {maxTokens}
259
- </label>
260
- <input
261
- type="range"
262
- min="10"
263
- max="500"
264
- step="10"
265
- value={maxTokens}
266
- onChange={(e) => setMaxTokens(parseInt(e.target.value))}
267
- className="w-full"
268
- />
269
- </div>
270
-
271
- <div>
272
- <label className="block text-sm font-medium text-gray-700 mb-1">
273
- Top P: {topP}
274
- </label>
275
- <input
276
- type="range"
277
- min="0.1"
278
- max="1.0"
279
- step="0.1"
280
- value={topP}
281
- onChange={(e) => setTopP(parseFloat(e.target.value))}
282
- className="w-full"
283
- />
284
- </div>
285
-
286
- <div>
287
- <label className="block text-sm font-medium text-gray-700 mb-1">
288
- Top K: {topK}
289
- </label>
290
- <input
291
- type="range"
292
- min="1"
293
- max="100"
294
- step="1"
295
- value={topK}
296
- onChange={(e) => setTopK(parseInt(e.target.value))}
297
- className="w-full"
298
- />
299
- </div>
300
-
301
- <div className="flex items-center">
302
- <Switch
303
- checked={doSample}
304
- onChange={setDoSample}
305
- className={`${
306
- doSample ? 'bg-blue-600' : 'bg-gray-200'
307
- } relative inline-flex h-6 w-11 items-center rounded-full`}
308
- >
309
- <span className="sr-only">Enable sampling</span>
310
- <span
311
- className={`${
312
- doSample ? 'translate-x-6' : 'translate-x-1'
313
- } inline-block h-4 w-4 transform rounded-full bg-white transition`}
314
- />
315
- </Switch>
316
- <label className="ml-2 text-sm font-medium text-gray-700">
317
- Do Sample
318
- </label>
319
- </div>
320
- </div>
321
- </div>
322
-
323
-
324
- {/* System Message for Chat */}
325
- {hasChatTemplate && (
326
- <div>
327
- <h4 className="font-semibold text-gray-800 mb-3">System Message</h4>
328
- <textarea
329
- value={messages.find(m => m.role === 'system')?.content || ''}
330
- onChange={(e) => updateSystemMessage(e.target.value)}
331
- className="w-full p-2 border border-gray-300 rounded-md text-sm"
332
- rows={3}
333
- placeholder="Enter system message..."
334
- />
335
- </div>
336
- )}
337
- </div>
338
-
339
- <div className="mt-6 flex justify-end">
340
- <button
341
- type="button"
342
- className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
343
- onClick={() => setShowSettings(false)}
344
- >
345
- Close
346
- </button>
347
- </div>
348
- </Dialog.Panel>
349
- </Transition.Child>
350
- </div>
351
- </div>
352
- </Dialog>
353
- </Transition>
354
-
355
  {hasChatTemplate ? (
356
- // Chat Layout
357
  <>
358
- {/* Chat Messages */}
359
  <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg p-4 mb-4 bg-white">
360
  <div className="space-y-4">
361
- {messages.filter(msg => msg.role !== 'system').map((message, index) => (
362
- <div
363
- key={index}
364
- className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
365
- >
366
  <div
367
- className={`max-w-[80%] p-3 rounded-lg ${
368
- message.role === 'user'
369
- ? 'bg-blue-500 text-white'
370
- : 'bg-gray-100 text-gray-800'
371
- }`}
372
  >
373
- <div className="text-xs font-medium mb-1 opacity-70">
374
- {message.role === 'user' ? 'You' : 'Assistant'}
 
 
 
 
 
 
 
 
 
 
 
375
  </div>
376
- <div className="whitespace-pre-wrap">{message.content}</div>
377
  </div>
378
- </div>
379
- ))}
380
  {isGenerating && (
381
  <div className="flex justify-start">
382
  <div className="bg-gray-100 text-gray-800 p-3 rounded-lg">
383
- <div className="text-xs font-medium mb-1 opacity-70">Assistant</div>
 
 
384
  <div className="flex items-center space-x-2">
385
  <Loader2 className="w-4 h-4 animate-spin" />
386
  <div>Loading...</div>
@@ -391,8 +217,6 @@ function TextGeneration() {
391
  </div>
392
  <div ref={messagesEndRef} />
393
  </div>
394
-
395
- {/* Chat Input Area */}
396
  <div className="flex gap-2">
397
  <textarea
398
  value={currentMessage}
@@ -417,9 +241,7 @@ function TextGeneration() {
417
  </div>
418
  </>
419
  ) : (
420
- // Simple Text Generation Layout
421
  <>
422
- {/* Prompt Input */}
423
  <div className="mb-4">
424
  <label className="block text-sm font-medium text-gray-700 mb-2">
425
  Enter your prompt:
@@ -434,8 +256,6 @@ function TextGeneration() {
434
  disabled={!hasBeenLoaded || isGenerating}
435
  />
436
  </div>
437
-
438
- {/* Generate Button */}
439
  <div className="mb-4">
440
  <button
441
  onClick={handleGenerateText}
@@ -455,8 +275,6 @@ function TextGeneration() {
455
  )}
456
  </button>
457
  </div>
458
-
459
- {/* Generated Text Output */}
460
  <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg p-4 bg-white">
461
  <div className="mb-2">
462
  <label className="block text-sm font-medium text-gray-700">
@@ -486,7 +304,8 @@ function TextGeneration() {
486
 
487
  {!hasBeenLoaded && (
488
  <div className="text-center text-gray-500 text-sm mt-2">
489
- Please load a model first to start {hasChatTemplate ? 'chatting' : 'generating text'}
 
490
  </div>
491
  )}
492
  </div>
 
1
  import { useState, useRef, useEffect, useCallback } from 'react'
2
+ import { Send, Trash2, Loader2, X } from 'lucide-react'
3
+ import { ChatMessage, TextGenerationWorkerInput, WorkerMessage } from '../types'
 
 
 
 
 
 
4
  import { useModel } from '../contexts/ModelContext'
5
+ import { useTextGeneration } from '../contexts/TextGenerationContext'
6
 
7
  function TextGeneration() {
8
+ const { config, messages, setMessages } = useTextGeneration()
9
+
 
10
  const [currentMessage, setCurrentMessage] = useState<string>('')
 
 
 
11
  const [prompt, setPrompt] = useState<string>('')
12
  const [generatedText, setGeneratedText] = useState<string>('')
 
 
 
 
 
 
 
 
 
13
  const [isGenerating, setIsGenerating] = useState<boolean>(false)
14
 
15
+ const {
16
+ activeWorker,
17
+ status,
18
+ modelInfo,
19
+ hasBeenLoaded,
20
+ selectedQuantization
21
+ } = useModel()
22
  const messagesEndRef = useRef<HTMLDivElement>(null)
23
 
24
  const scrollToBottom = () => {
 
37
  }, [activeWorker, isGenerating])
38
 
39
  const handleSendMessage = useCallback(() => {
40
+ if (!currentMessage.trim() || !modelInfo || !activeWorker || isGenerating)
41
  return
 
42
 
43
  const userMessage: ChatMessage = {
44
  role: 'user',
45
  content: currentMessage.trim()
46
  }
 
47
  const updatedMessages = [...messages, userMessage]
48
  setMessages(updatedMessages)
49
  setCurrentMessage('')
 
54
  messages: updatedMessages,
55
  hasChatTemplate: modelInfo.hasChatTemplate,
56
  model: modelInfo.id,
57
+ temperature: config.temperature,
58
+ max_new_tokens: config.maxTokens,
59
+ top_p: config.topP,
60
+ top_k: config.topK,
61
+ do_sample: config.doSample,
62
  dtype: selectedQuantization ?? 'fp32'
63
  }
64
 
65
  activeWorker.postMessage(message)
66
+ }, [
67
+ currentMessage,
68
+ messages,
69
+ setMessages,
70
+ modelInfo,
71
+ activeWorker,
72
+ config,
73
+ isGenerating,
74
+ selectedQuantization
75
+ ])
76
 
77
  const handleGenerateText = useCallback(() => {
78
+ if (!prompt.trim() || !modelInfo || !activeWorker || isGenerating) return
 
 
 
79
  setIsGenerating(true)
80
 
81
  const message: TextGenerationWorkerInput = {
 
83
  prompt: prompt.trim(),
84
  hasChatTemplate: modelInfo.hasChatTemplate,
85
  model: modelInfo.id,
86
+ temperature: config.temperature,
87
+ max_new_tokens: config.maxTokens,
88
+ top_p: config.topP,
89
+ top_k: config.topK,
90
+ do_sample: config.doSample,
91
  dtype: selectedQuantization ?? 'fp32'
92
  }
93
 
94
  activeWorker.postMessage(message)
95
+ }, [
96
+ prompt,
97
+ modelInfo,
98
+ activeWorker,
99
+ config,
100
+ isGenerating,
101
+ selectedQuantization
102
+ ])
103
 
104
  useEffect(() => {
105
  if (!activeWorker) return
 
106
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
107
  const { status, output } = e.data
 
108
  if (status === 'output' && output) {
109
  setIsGenerating(false)
110
  if (modelInfo?.hasChatTemplate) {
 
111
  const assistantMessage: ChatMessage = {
112
  role: 'assistant',
113
  content: output.content
114
  }
115
+ setMessages((prev) => [...prev, assistantMessage])
116
  } else {
 
117
  setGeneratedText(output.content)
118
  }
119
+ } else if (status === 'ready' || status === 'error') {
 
 
120
  setIsGenerating(false)
121
  }
122
  }
 
123
  activeWorker.addEventListener('message', onMessageReceived)
124
+ return () => activeWorker.removeEventListener('message', onMessageReceived)
125
+ }, [activeWorker, modelInfo?.hasChatTemplate, setMessages])
 
 
 
126
 
127
  const handleKeyPress = (e: React.KeyboardEvent) => {
128
  if (e.key === 'Enter' && !e.shiftKey) {
129
  e.preventDefault()
130
+
131
  if (modelInfo?.hasChatTemplate) {
132
  handleSendMessage()
133
  } else {
 
138
 
139
  const clearChat = () => {
140
  if (modelInfo?.hasChatTemplate) {
141
+ setMessages((prev) => prev.filter((msg) => msg.role === 'system'))
142
  } else {
143
  setPrompt('')
144
  setGeneratedText('')
145
  }
146
  }
147
 
 
 
 
 
 
 
 
 
148
  const busy = status !== 'ready' || isGenerating
149
  const hasChatTemplate = modelInfo?.hasChatTemplate
150
 
 
155
  {hasChatTemplate ? 'Chat with AI' : 'Text Generation'}
156
  </h1>
157
  <div className="flex gap-2">
 
 
 
 
 
 
 
158
  <button
159
  onClick={clearChat}
160
  className="p-2 bg-red-100 hover:bg-red-200 rounded-lg transition-colors"
161
+ title={hasChatTemplate ? 'Clear Chat' : 'Clear Text'}
162
  >
163
  <Trash2 className="w-4 h-4" />
164
  </button>
 
174
  </div>
175
  </div>
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  {hasChatTemplate ? (
 
178
  <>
 
179
  <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg p-4 mb-4 bg-white">
180
  <div className="space-y-4">
181
+ {messages
182
+ .filter((msg) => msg.role !== 'system')
183
+ .map((message, index) => (
 
 
184
  <div
185
+ key={index}
186
+ className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
 
 
 
187
  >
188
+ <div
189
+ className={`max-w-[80%] p-3 rounded-lg ${
190
+ message.role === 'user'
191
+ ? 'bg-blue-500 text-white'
192
+ : 'bg-gray-100 text-gray-800'
193
+ }`}
194
+ >
195
+ <div className="text-xs font-medium mb-1 opacity-70">
196
+ {message.role === 'user' ? 'You' : 'Assistant'}
197
+ </div>
198
+ <div className="whitespace-pre-wrap">
199
+ {message.content}
200
+ </div>
201
  </div>
 
202
  </div>
203
+ ))}
 
204
  {isGenerating && (
205
  <div className="flex justify-start">
206
  <div className="bg-gray-100 text-gray-800 p-3 rounded-lg">
207
+ <div className="text-xs font-medium mb-1 opacity-70">
208
+ Assistant
209
+ </div>
210
  <div className="flex items-center space-x-2">
211
  <Loader2 className="w-4 h-4 animate-spin" />
212
  <div>Loading...</div>
 
217
  </div>
218
  <div ref={messagesEndRef} />
219
  </div>
 
 
220
  <div className="flex gap-2">
221
  <textarea
222
  value={currentMessage}
 
241
  </div>
242
  </>
243
  ) : (
 
244
  <>
 
245
  <div className="mb-4">
246
  <label className="block text-sm font-medium text-gray-700 mb-2">
247
  Enter your prompt:
 
256
  disabled={!hasBeenLoaded || isGenerating}
257
  />
258
  </div>
 
 
259
  <div className="mb-4">
260
  <button
261
  onClick={handleGenerateText}
 
275
  )}
276
  </button>
277
  </div>
 
 
278
  <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg p-4 bg-white">
279
  <div className="mb-2">
280
  <label className="block text-sm font-medium text-gray-700">
 
304
 
305
  {!hasBeenLoaded && (
306
  <div className="text-center text-gray-500 text-sm mt-2">
307
+ Please load a model first to start{' '}
308
+ {hasChatTemplate ? 'chatting' : 'generating text'}
309
  </div>
310
  )}
311
  </div>
src/components/TextGenerationConfig.tsx ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Switch } from '@headlessui/react'
2
+ import {
3
+ useTextGeneration,
4
+ GenerationConfigState
5
+ } from '../contexts/TextGenerationContext'
6
+ import { useModel } from '../contexts/ModelContext'
7
+
8
+ function TextGenerationConfig() {
9
+ const { config, setConfig, messages, updateSystemMessage } =
10
+ useTextGeneration()
11
+ const { modelInfo } = useModel()
12
+
13
+ const handleConfigChange = (
14
+ field: keyof GenerationConfigState,
15
+ value: number | boolean
16
+ ) => {
17
+ setConfig((prev) => ({ ...prev, [field]: value }))
18
+ }
19
+
20
+ return (
21
+ <div className="space-y-6">
22
+ <h3 className="text-lg font-semibold text-gray-900">
23
+ Text Generation Settings
24
+ </h3>
25
+
26
+ {/* Generation Parameters */}
27
+ <div className="space-y-4 px-10">
28
+ <div>
29
+ <label className="block text-sm font-medium text-gray-700 mb-1">
30
+ Temperature: {config.temperature}
31
+ </label>
32
+ <input
33
+ type="range"
34
+ min="0.1"
35
+ max="2.0"
36
+ step="0.1"
37
+ value={config.temperature}
38
+ onChange={(e) =>
39
+ handleConfigChange('temperature', parseFloat(e.target.value))
40
+ }
41
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
42
+ />
43
+ </div>
44
+
45
+ <div>
46
+ <label className="block text-sm font-medium text-gray-700 mb-1">
47
+ Max Tokens: {config.maxTokens}
48
+ </label>
49
+ <input
50
+ type="range"
51
+ min="10"
52
+ max="500"
53
+ step="10"
54
+ value={config.maxTokens}
55
+ onChange={(e) =>
56
+ handleConfigChange('maxTokens', parseInt(e.target.value))
57
+ }
58
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
59
+ />
60
+ </div>
61
+
62
+ <div>
63
+ <label className="block text-sm font-medium text-gray-700 mb-1">
64
+ Top-p: {config.topP}
65
+ </label>
66
+ <input
67
+ type="range"
68
+ min="0.1"
69
+ max="1.0"
70
+ step="0.1"
71
+ value={config.topP}
72
+ onChange={(e) =>
73
+ handleConfigChange('topP', parseFloat(e.target.value))
74
+ }
75
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
76
+ />
77
+ </div>
78
+
79
+ <div>
80
+ <label className="block text-sm font-medium text-gray-700 mb-1">
81
+ Top-k: {config.topK}
82
+ </label>
83
+ <input
84
+ type="range"
85
+ min="1"
86
+ max="100"
87
+ step="1"
88
+ value={config.topK}
89
+ onChange={(e) =>
90
+ handleConfigChange('topK', parseInt(e.target.value))
91
+ }
92
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
93
+ />
94
+ </div>
95
+
96
+ <div className="flex items-center pt-2">
97
+ <Switch
98
+ checked={config.doSample}
99
+ onChange={(checked) => handleConfigChange('doSample', checked)}
100
+ className={`${config.doSample ? 'bg-blue-600' : 'bg-gray-200'}
101
+ relative inline-flex h-6 w-11 items-center rounded-full`}
102
+ >
103
+ <span
104
+ className={`${config.doSample ? 'translate-x-6' : 'translate-x-1'}
105
+ inline-block h-4 w-4 transform rounded-full bg-white transition`}
106
+ />
107
+ </Switch>
108
+ <label className="ml-3 text-sm font-medium text-gray-700">
109
+ Do Sample
110
+ </label>
111
+ </div>
112
+ </div>
113
+
114
+ {/* System Message for Chat */}
115
+ {modelInfo?.hasChatTemplate && (
116
+ <div>
117
+ <h4 className="font-semibold text-gray-800 mb-2">System Message</h4>
118
+ <textarea
119
+ value={messages.find((m) => m.role === 'system')?.content || ''}
120
+ onChange={(e) => updateSystemMessage(e.target.value)}
121
+ className="w-full p-2 border border-gray-300 rounded-md text-sm"
122
+ rows={4}
123
+ placeholder="e.g., You are a helpful assistant."
124
+ />
125
+ </div>
126
+ )}
127
+ </div>
128
+ )
129
+ }
130
+
131
+ export default TextGenerationConfig
src/contexts/TextGenerationContext.tsx ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useContext, useState, ReactNode } from 'react'
2
+ import { ChatMessage } from '../types'
3
+
4
+ export interface GenerationConfigState {
5
+ temperature: number
6
+ maxTokens: number
7
+ topP: number
8
+ topK: number
9
+ doSample: boolean
10
+ }
11
+
12
+ interface TextGenerationContextType {
13
+ config: GenerationConfigState
14
+ setConfig: React.Dispatch<React.SetStateAction<GenerationConfigState>>
15
+ messages: ChatMessage[]
16
+ setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>
17
+ updateSystemMessage: (content: string) => void
18
+ }
19
+
20
+ const TextGenerationContext = createContext<
21
+ TextGenerationContextType | undefined
22
+ >(undefined)
23
+
24
+ export function TextGenerationProvider({ children }: { children: ReactNode }) {
25
+ const [config, setConfig] = useState<GenerationConfigState>({
26
+ temperature: 0.7,
27
+ maxTokens: 100,
28
+ topP: 0.9,
29
+ topK: 50,
30
+ doSample: true
31
+ })
32
+
33
+ const [messages, setMessages] = useState<ChatMessage[]>([
34
+ { role: 'system', content: 'You are a helpful assistant.' }
35
+ ])
36
+
37
+ const updateSystemMessage = (content: string) => {
38
+ setMessages((prev) => [
39
+ { role: 'system', content },
40
+ ...prev.filter((msg) => msg.role !== 'system')
41
+ ])
42
+ }
43
+
44
+ const value = {
45
+ config,
46
+ setConfig,
47
+ messages,
48
+ setMessages,
49
+ updateSystemMessage
50
+ }
51
+
52
+ return (
53
+ <TextGenerationContext.Provider value={value}>
54
+ {children}
55
+ </TextGenerationContext.Provider>
56
+ )
57
+ }
58
+
59
+ export function useTextGeneration() {
60
+ const context = useContext(TextGenerationContext)
61
+ if (context === undefined) {
62
+ throw new Error(
63
+ 'useTextGeneration must be used within a TextGenerationProvider'
64
+ )
65
+ }
66
+ return context
67
+ }