kaiiddo commited on
Commit
2b4dda3
·
verified ·
1 Parent(s): 1395cc4

Update src/App.js

Browse files
Files changed (1) hide show
  1. src/App.js +89 -83
src/App.js CHANGED
@@ -1,5 +1,6 @@
1
  import React, { useState, useRef, useEffect } from 'react';
2
  import { modelCompanies, allModels, findModelById } from './models/modelConfig';
 
3
  import './App.css';
4
 
5
  function App() {
@@ -16,8 +17,21 @@ function App() {
16
  const messagesEndRef = useRef(null);
17
  const textareaRef = useRef(null);
18
  const dropdownRef = useRef(null);
 
19
 
20
- // Check for stored token on component mount
 
 
 
 
 
 
 
 
 
 
 
 
21
  useEffect(() => {
22
  const storedToken = localStorage.getItem('hf_token');
23
  if (storedToken) {
@@ -26,11 +40,7 @@ function App() {
26
  }
27
  }, []);
28
 
29
- // Scroll to bottom of messages
30
- const scrollToBottom = () => {
31
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
32
- };
33
-
34
  useEffect(() => {
35
  scrollToBottom();
36
  }, [messages]);
@@ -42,7 +52,6 @@ function App() {
42
  setShowModelDropdown(false);
43
  }
44
  };
45
-
46
  document.addEventListener('mousedown', handleClickOutside);
47
  return () => document.removeEventListener('mousedown', handleClickOutside);
48
  }, []);
@@ -64,18 +73,19 @@ function App() {
64
  }
65
  }, [isDarkMode]);
66
 
 
 
 
 
67
  const handleTokenSubmit = () => {
68
  if (!hfToken.trim()) {
69
  setError('Please enter your Hugging Face token');
70
  return;
71
  }
72
-
73
- // Basic token validation (HF tokens typically start with "hf_")
74
  if (!hfToken.startsWith('hf_')) {
75
  setError('Please enter a valid Hugging Face token');
76
  return;
77
  }
78
-
79
  localStorage.setItem('hf_token', hfToken.trim());
80
  setShowAuth(false);
81
  setError('');
@@ -103,45 +113,65 @@ function App() {
103
  setIsLoading(true);
104
  setError('');
105
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  try {
107
  const currentModelConfig = findModelById(selectedModel);
 
108
 
109
- // Simulate API call - Replace this with actual Hugging Face Inference API call
110
- const response = await simulateHuggingFaceCall(inputValue.trim(), currentModelConfig, hfToken);
111
-
112
- const aiMessage = {
113
- id: Date.now() + 1,
114
- content: response,
115
- role: 'assistant',
116
- timestamp: new Date()
117
- };
118
- setMessages(prev => [...prev, aiMessage]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  } catch (err) {
120
- setError('Failed to get response from AI model. Please check your token and try again.');
121
- console.error('API Error:', err);
122
- } finally {
123
  setIsLoading(false);
 
 
124
  }
125
  };
126
 
127
- // Simulate Hugging Face API call - Replace with actual implementation
128
- const simulateHuggingFaceCall = async (message, modelConfig, token) => {
129
- // This is a simulation - replace with actual Hugging Face Inference API
130
- return new Promise((resolve) => {
131
- setTimeout(() => {
132
- resolve(`This is a simulated response from ${modelConfig.name} (${modelConfig.company}).
133
-
134
- In a real implementation, this would connect to the Hugging Face Inference API using:
135
-
136
- Model: ${modelConfig.endpoint}
137
- Provider: ${modelConfig.provider}
138
- Token: ${token.substring(0, 10)}...
139
-
140
- Your message: "${message}"`);
141
- }, 2000);
142
- });
143
- };
144
-
145
  const handleKeyPress = (e) => {
146
  if (e.key === 'Enter' && !e.shiftKey) {
147
  e.preventDefault();
@@ -155,17 +185,16 @@ Your message: "${message}"`);
155
  };
156
 
157
  const currentModel = findModelById(selectedModel);
158
-
159
- // Group models by company for dropdown
160
  const groupedModels = modelCompanies;
161
 
 
162
  if (showAuth) {
163
  return (
164
  <div className={`App ${isDarkMode ? 'dark' : ''}`}>
165
  <div className="auth-modal">
166
  <div className="auth-content">
167
  <div className="logo" style={{ justifyContent: 'center', marginBottom: '24px' }}>
168
- <i data-lucide="brain" width="32" height="32"></i>
169
  <span style={{ fontSize: '28px' }}>SynapseAI</span>
170
  </div>
171
 
@@ -176,7 +205,7 @@ Your message: "${message}"`);
176
 
177
  {error && (
178
  <div className="error-message">
179
- <i data-lucide="alert-circle" width="16" height="16" style={{ marginRight: '8px' }}></i>
180
  {error}
181
  </div>
182
  )}
@@ -194,7 +223,7 @@ Your message: "${message}"`);
194
 
195
  <div className="auth-actions">
196
  <button className="btn primary" onClick={handleTokenSubmit}>
197
- <i data-lucide="key" width="16" height="16"></i>
198
  Start Chatting
199
  </button>
200
  </div>
@@ -211,16 +240,6 @@ Your message: "${message}"`);
211
  </div>
212
  </div>
213
  </div>
214
-
215
- <script
216
- dangerouslySetInnerHTML={{
217
- __html: `
218
- if (window.lucide) {
219
- window.lucide.createIcons();
220
- }
221
- `
222
- }}
223
- />
224
  </div>
225
  );
226
  }
@@ -231,16 +250,16 @@ Your message: "${message}"`);
231
  {/* Header */}
232
  <header className="header">
233
  <div className="logo">
234
- <i data-lucide="brain" width="28" height="28"></i>
235
  <span>SynapseAI</span>
236
  </div>
237
 
238
  <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
239
  <div className="token-display">
240
- <i data-lucide="key" width="14" height="14"></i>
241
  <span className="token-text">Token: {hfToken.substring(0, 10)}...</span>
242
  <div className="clear-token" onClick={handleClearToken} title="Clear token">
243
- <i data-lucide="x" width="14" height="14"></i>
244
  </div>
245
  </div>
246
 
@@ -248,7 +267,7 @@ Your message: "${message}"`);
248
  className="btn ghost theme-toggle"
249
  onClick={() => setIsDarkMode(!isDarkMode)}
250
  >
251
- <i data-lucide={isDarkMode ? "sun" : "moon"} width="20" height="20"></i>
252
  </button>
253
  </div>
254
  </header>
@@ -258,10 +277,10 @@ Your message: "${message}"`);
258
  {messages.length === 0 && (
259
  <div className="welcome-message">
260
  <div className="card" style={{ maxWidth: '600px', margin: '0 auto' }}>
261
- <i data-lucide="sparkles" width="48" height="48" style={{ marginBottom: '16px', color: '#71717a' }}></i>
262
  <h2 style={{ marginBottom: '8px', fontSize: '24px', fontWeight: '600' }}>Welcome to SynapseAI</h2>
263
  <p style={{ color: '#71717a', lineHeight: '1.5', marginBottom: '16px' }}>
264
- Start a conversation with our AI models. Select your preferred model from the dropdown below.
265
  </p>
266
  <p style={{ fontSize: '14px', color: '#a1a1aa' }}>
267
  Current Model: <strong>{currentModel?.name}</strong> by {currentModel?.company}
@@ -273,12 +292,12 @@ Your message: "${message}"`);
273
  {messages.map((message) => (
274
  <div key={message.id} className={`message ${message.role}`}>
275
  <div className="message-content">
276
- {message.content}
277
  </div>
278
  </div>
279
  ))}
280
 
281
- {isLoading && (
282
  <div className="message assistant">
283
  <div className="typing-indicator">
284
  <div className="typing-dot"></div>
@@ -294,7 +313,7 @@ Your message: "${message}"`);
294
  {/* Error Display */}
295
  {error && (
296
  <div className="error-message">
297
- <i data-lucide="alert-circle" width="16" height="16" style={{ marginRight: '8px' }}></i>
298
  {error}
299
  </div>
300
  )}
@@ -308,9 +327,9 @@ Your message: "${message}"`);
308
  className="btn model-selector"
309
  onClick={() => setShowModelDropdown(!showModelDropdown)}
310
  >
311
- <i data-lucide={currentModel?.companyLogo || "cpu"} width="16" height="16"></i>
312
  <span style={{ flex: 1, textAlign: 'left' }}>{currentModel?.name}</span>
313
- <i data-lucide="chevron-down" width="16" height="16"></i>
314
  </button>
315
 
316
  {showModelDropdown && (
@@ -318,7 +337,7 @@ Your message: "${message}"`);
318
  {groupedModels.map((company) => (
319
  <div key={company.id} className="company-section">
320
  <div className="company-header">
321
- <i data-lucide={company.logo} width="16" height="16"></i>
322
  {company.name}
323
  </div>
324
  {company.models.map((model) => (
@@ -332,9 +351,7 @@ Your message: "${message}"`);
332
  <div className="model-description">{model.description}</div>
333
  </div>
334
  <div className="model-check">
335
- {selectedModel === model.id && (
336
- <i data-lucide="check" width="16" height="16"></i>
337
- )}
338
  </div>
339
  </div>
340
  ))}
@@ -362,22 +379,11 @@ Your message: "${message}"`);
362
  onClick={handleSendMessage}
363
  disabled={!inputValue.trim() || isLoading}
364
  >
365
- <i data-lucide="send" width="18" height="18"></i>
366
  </button>
367
  </div>
368
  </div>
369
  </div>
370
-
371
- {/* Initialize Lucide Icons */}
372
- <script
373
- dangerouslySetInnerHTML={{
374
- __html: `
375
- if (window.lucide) {
376
- window.lucide.createIcons();
377
- }
378
- `
379
- }}
380
- />
381
  </div>
382
  );
383
  }
 
1
  import React, { useState, useRef, useEffect } from 'react';
2
  import { modelCompanies, allModels, findModelById } from './models/modelConfig';
3
+ import { HuggingFaceService } from './services/huggingfaceService';
4
  import './App.css';
5
 
6
  function App() {
 
17
  const messagesEndRef = useRef(null);
18
  const textareaRef = useRef(null);
19
  const dropdownRef = useRef(null);
20
+ const currentMessageRef = useRef(null);
21
 
22
+ // Initialize Lucide icons
23
+ useEffect(() => {
24
+ const initIcons = () => {
25
+ if (window.lucide) {
26
+ window.lucide.createIcons();
27
+ } else {
28
+ setTimeout(initIcons, 100);
29
+ }
30
+ };
31
+ initIcons();
32
+ }, []);
33
+
34
+ // Check for stored token
35
  useEffect(() => {
36
  const storedToken = localStorage.getItem('hf_token');
37
  if (storedToken) {
 
40
  }
41
  }, []);
42
 
43
+ // Scroll to bottom
 
 
 
 
44
  useEffect(() => {
45
  scrollToBottom();
46
  }, [messages]);
 
52
  setShowModelDropdown(false);
53
  }
54
  };
 
55
  document.addEventListener('mousedown', handleClickOutside);
56
  return () => document.removeEventListener('mousedown', handleClickOutside);
57
  }, []);
 
73
  }
74
  }, [isDarkMode]);
75
 
76
+ const scrollToBottom = () => {
77
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
78
+ };
79
+
80
  const handleTokenSubmit = () => {
81
  if (!hfToken.trim()) {
82
  setError('Please enter your Hugging Face token');
83
  return;
84
  }
 
 
85
  if (!hfToken.startsWith('hf_')) {
86
  setError('Please enter a valid Hugging Face token');
87
  return;
88
  }
 
89
  localStorage.setItem('hf_token', hfToken.trim());
90
  setShowAuth(false);
91
  setError('');
 
113
  setIsLoading(true);
114
  setError('');
115
 
116
+ // Create assistant message for streaming
117
+ const assistantMessageId = Date.now() + 1;
118
+ const assistantMessage = {
119
+ id: assistantMessageId,
120
+ content: '',
121
+ role: 'assistant',
122
+ timestamp: new Date()
123
+ };
124
+
125
+ setMessages(prev => [...prev, assistantMessage]);
126
+ currentMessageRef.current = assistantMessageId;
127
+
128
  try {
129
  const currentModelConfig = findModelById(selectedModel);
130
+ const hfService = new HuggingFaceService(hfToken);
131
 
132
+ const chatMessages = [
133
+ ...messages.filter(msg => msg.role !== 'assistant' || msg.content),
134
+ userMessage
135
+ ].map(msg => ({
136
+ role: msg.role,
137
+ content: msg.content
138
+ }));
139
+
140
+ await hfService.streamChatCompletionAlt(
141
+ chatMessages,
142
+ currentModelConfig,
143
+ (chunk) => {
144
+ // Update message content with streaming chunks
145
+ setMessages(prev => prev.map(msg =>
146
+ msg.id === assistantMessageId
147
+ ? { ...msg, content: msg.content + chunk }
148
+ : msg
149
+ ));
150
+ },
151
+ () => {
152
+ // Streaming completed
153
+ setIsLoading(false);
154
+ currentMessageRef.current = null;
155
+ },
156
+ (errorMsg) => {
157
+ // Error handling
158
+ setError(`Model error: ${errorMsg}`);
159
+ setIsLoading(false);
160
+ currentMessageRef.current = null;
161
+
162
+ // Remove empty assistant message on error
163
+ setMessages(prev => prev.filter(msg => msg.id !== assistantMessageId));
164
+ }
165
+ );
166
+
167
  } catch (err) {
168
+ setError('Failed to connect to AI model');
 
 
169
  setIsLoading(false);
170
+ currentMessageRef.current = null;
171
+ setMessages(prev => prev.filter(msg => msg.id !== assistantMessageId));
172
  }
173
  };
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  const handleKeyPress = (e) => {
176
  if (e.key === 'Enter' && !e.shiftKey) {
177
  e.preventDefault();
 
185
  };
186
 
187
  const currentModel = findModelById(selectedModel);
 
 
188
  const groupedModels = modelCompanies;
189
 
190
+ // Auth Modal
191
  if (showAuth) {
192
  return (
193
  <div className={`App ${isDarkMode ? 'dark' : ''}`}>
194
  <div className="auth-modal">
195
  <div className="auth-content">
196
  <div className="logo" style={{ justifyContent: 'center', marginBottom: '24px' }}>
197
+ <i data-lucide="brain"></i>
198
  <span style={{ fontSize: '28px' }}>SynapseAI</span>
199
  </div>
200
 
 
205
 
206
  {error && (
207
  <div className="error-message">
208
+ <i data-lucide="alert-circle"></i>
209
  {error}
210
  </div>
211
  )}
 
223
 
224
  <div className="auth-actions">
225
  <button className="btn primary" onClick={handleTokenSubmit}>
226
+ <i data-lucide="key"></i>
227
  Start Chatting
228
  </button>
229
  </div>
 
240
  </div>
241
  </div>
242
  </div>
 
 
 
 
 
 
 
 
 
 
243
  </div>
244
  );
245
  }
 
250
  {/* Header */}
251
  <header className="header">
252
  <div className="logo">
253
+ <i data-lucide="brain"></i>
254
  <span>SynapseAI</span>
255
  </div>
256
 
257
  <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
258
  <div className="token-display">
259
+ <i data-lucide="key"></i>
260
  <span className="token-text">Token: {hfToken.substring(0, 10)}...</span>
261
  <div className="clear-token" onClick={handleClearToken} title="Clear token">
262
+ <i data-lucide="x"></i>
263
  </div>
264
  </div>
265
 
 
267
  className="btn ghost theme-toggle"
268
  onClick={() => setIsDarkMode(!isDarkMode)}
269
  >
270
+ <i data-lucide={isDarkMode ? "sun" : "moon"}></i>
271
  </button>
272
  </div>
273
  </header>
 
277
  {messages.length === 0 && (
278
  <div className="welcome-message">
279
  <div className="card" style={{ maxWidth: '600px', margin: '0 auto' }}>
280
+ <i data-lucide="sparkles"></i>
281
  <h2 style={{ marginBottom: '8px', fontSize: '24px', fontWeight: '600' }}>Welcome to SynapseAI</h2>
282
  <p style={{ color: '#71717a', lineHeight: '1.5', marginBottom: '16px' }}>
283
+ Start a conversation with AI models. Select your preferred model below.
284
  </p>
285
  <p style={{ fontSize: '14px', color: '#a1a1aa' }}>
286
  Current Model: <strong>{currentModel?.name}</strong> by {currentModel?.company}
 
292
  {messages.map((message) => (
293
  <div key={message.id} className={`message ${message.role}`}>
294
  <div className="message-content">
295
+ {message.content || (message.role === 'assistant' && isLoading && '...')}
296
  </div>
297
  </div>
298
  ))}
299
 
300
+ {isLoading && !currentMessageRef.current && (
301
  <div className="message assistant">
302
  <div className="typing-indicator">
303
  <div className="typing-dot"></div>
 
313
  {/* Error Display */}
314
  {error && (
315
  <div className="error-message">
316
+ <i data-lucide="alert-circle"></i>
317
  {error}
318
  </div>
319
  )}
 
327
  className="btn model-selector"
328
  onClick={() => setShowModelDropdown(!showModelDropdown)}
329
  >
330
+ <i data-lucide={currentModel?.companyLogo || "cpu"}></i>
331
  <span style={{ flex: 1, textAlign: 'left' }}>{currentModel?.name}</span>
332
+ <i data-lucide="chevron-down"></i>
333
  </button>
334
 
335
  {showModelDropdown && (
 
337
  {groupedModels.map((company) => (
338
  <div key={company.id} className="company-section">
339
  <div className="company-header">
340
+ <i data-lucide={company.logo}></i>
341
  {company.name}
342
  </div>
343
  {company.models.map((model) => (
 
351
  <div className="model-description">{model.description}</div>
352
  </div>
353
  <div className="model-check">
354
+ {selectedModel === model.id && <i data-lucide="check"></i>}
 
 
355
  </div>
356
  </div>
357
  ))}
 
379
  onClick={handleSendMessage}
380
  disabled={!inputValue.trim() || isLoading}
381
  >
382
+ <i data-lucide="send"></i>
383
  </button>
384
  </div>
385
  </div>
386
  </div>
 
 
 
 
 
 
 
 
 
 
 
387
  </div>
388
  );
389
  }