-
Notifications
You must be signed in to change notification settings - Fork 0
[WIP] Setting up Frontend for SLM/LLM Infrastructure #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b576fac
486a902
a00c34c
2dd3708
20f13f4
58320cf
d32dfd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||
| import React from 'react'; | ||||||||||||
| import { SettingsProvider } from './stores/settingsStore'; | ||||||||||||
| import { Navigation } from './components/Navigation'; | ||||||||||||
| import { SettingsPage } from './pages/SettingsPage'; | ||||||||||||
| import { DashboardPage } from './pages/DashboardPage'; | ||||||||||||
| import './styles.css'; | ||||||||||||
|
|
||||||||||||
| function App() { | ||||||||||||
| // Simple routing based on hash | ||||||||||||
| const currentHash = window.location.hash.slice(1) || '/'; | ||||||||||||
|
|
||||||||||||
| const renderPage = () => { | ||||||||||||
| switch (currentHash) { | ||||||||||||
| case '/settings': | ||||||||||||
| return <SettingsPage />; | ||||||||||||
| case '/': | ||||||||||||
| default: | ||||||||||||
| return <DashboardPage />; | ||||||||||||
| } | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| // Update navigation links to use hash routing | ||||||||||||
| React.useEffect(() => { | ||||||||||||
| const handleHashChange = () => { | ||||||||||||
| // Force re-render when hash changes | ||||||||||||
| window.location.reload(); | ||||||||||||
|
||||||||||||
| window.location.reload(); | |
| // Update state to trigger re-render when hash changes | |
| setCurrentHash(window.location.hash.slice(1) || '/'); |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using window.location.reload() on hash changes causes a full page reload, which is inefficient for a SPA. Consider using React state to trigger re-renders instead of reloading the entire page.
| useEffect(() => { | |
| const handleHashChange = () => { | |
| setCurrentHash(window.location.hash.slice(1) || '/'); | |
| }; |
Copilot
AI
Aug 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using window.location.reload() forces a full page reload on hash changes, which is inefficient and will lose application state. Consider using React state to trigger re-renders instead of reloading the entire page.
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using window.location.reload() for hash routing causes unnecessary full page reloads. This should use React state to trigger re-renders instead of reloading the entire page.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface NavItemProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isActive?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const NavItem: React.FC<NavItemProps> = ({ href, children, isActive = false }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={href} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isActive | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? 'bg-blue-100 text-blue-700' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const Navigation: React.FC = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentHash = window.location.hash.slice(1) || '/'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [currentHash, setCurrentHash] = useState(() => window.location.hash.slice(1) || '/'); | |
| useEffect(() => { | |
| const onHashChange = () => { | |
| setCurrentHash(window.location.hash.slice(1) || '/'); | |
| }; | |
| window.addEventListener('hashchange', onHashChange); | |
| return () => { | |
| window.removeEventListener('hashchange', onHashChange); | |
| }; | |
| }, []); |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading window.location.hash directly in render creates a static value that won't update when the hash changes. This should use React state or a custom hook to track hash changes reactively.
| const [currentHash, setCurrentHash] = React.useState(() => window.location.hash.slice(1) || '/'); | |
| React.useEffect(() => { | |
| const onHashChange = () => { | |
| setCurrentHash(window.location.hash.slice(1) || '/'); | |
| }; | |
| window.addEventListener('hashchange', onHashChange); | |
| return () => { | |
| window.removeEventListener('hashchange', onHashChange); | |
| }; | |
| }, []); |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading window.location.hash directly in render without state tracking will not trigger re-renders when the hash changes. This should be managed through React state or a proper routing library.
| const [currentHash, setCurrentHash] = useState(() => window.location.hash.slice(1) || '/'); | |
| useEffect(() => { | |
| const onHashChange = () => { | |
| setCurrentHash(window.location.hash.slice(1) || '/'); | |
| }; | |
| window.addEventListener('hashchange', onHashChange); | |
| return () => { | |
| window.removeEventListener('hashchange', onHashChange); | |
| }; | |
| }, []); |
Copilot
AI
Aug 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading window.location.hash directly in render can cause hydration mismatches and doesn't update when the hash changes. This should be moved to a state variable that updates on hash changes.
| const [currentHash, setCurrentHash] = useState<string>('/'); | |
| useEffect(() => { | |
| // Set initial hash on mount (client-side only) | |
| const getHash = () => window.location.hash.slice(1) || '/'; | |
| setCurrentHash(getHash()); | |
| const onHashChange = () => { | |
| setCurrentHash(getHash()); | |
| }; | |
| window.addEventListener('hashchange', onHashChange); | |
| return () => { | |
| window.removeEventListener('hashchange', onHashChange); | |
| }; | |
| }, []); |
Check failure
Code scanning / check-spelling
Unrecognized Spelling Error
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import React from 'react'; | ||
| import { useSettings } from '../../stores/settingsStore'; | ||
| import { BackendOption } from '../../types/settings'; | ||
|
|
||
| const BackendCard: React.FC<{ option: BackendOption; isSelected: boolean; onSelect: () => void }> = ({ | ||
| option, | ||
| isSelected, | ||
| onSelect, | ||
| }) => { | ||
| return ( | ||
| <div | ||
| className={`border rounded-lg p-4 cursor-pointer transition-all duration-200 ${ | ||
| isSelected | ||
| ? 'border-blue-500 bg-blue-50 ring-2 ring-blue-200' | ||
| : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' | ||
| } ${!option.isAvailable ? 'opacity-50 cursor-not-allowed' : ''}`} | ||
| onClick={option.isAvailable ? onSelect : undefined} | ||
| > | ||
| <div className="flex items-center justify-between mb-2"> | ||
| <h3 className="text-lg font-semibold text-gray-900">{option.name}</h3> | ||
| <div className="flex items-center space-x-2"> | ||
| <span | ||
| className={`px-2 py-1 text-xs font-medium rounded-full ${ | ||
| option.type === 'LLM' | ||
| ? 'bg-purple-100 text-purple-800' | ||
| : 'bg-green-100 text-green-800' | ||
| }`} | ||
| > | ||
| {option.type} | ||
| </span> | ||
| <span | ||
| className={`px-2 py-1 text-xs font-medium rounded-full ${ | ||
| option.isAvailable | ||
| ? 'bg-green-100 text-green-800' | ||
| : 'bg-red-100 text-red-800' | ||
| }`} | ||
| > | ||
| {option.isAvailable ? 'Available' : 'Unavailable'} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| <p className="text-gray-600 text-sm mb-2">{option.description}</p> | ||
| {option.provider && ( | ||
| <p className="text-gray-500 text-xs">Provider: {option.provider}</p> | ||
| )} | ||
| {option.endpoint && ( | ||
| <p className="text-gray-500 text-xs">Endpoint: {option.endpoint}</p> | ||
| )} | ||
| {isSelected && ( | ||
| <div className="mt-3 flex items-center"> | ||
| <svg className="w-4 h-4 text-blue-500 mr-2" fill="currentColor" viewBox="0 0 20 20"> | ||
| <path | ||
| fillRule="evenodd" | ||
| d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| <span className="text-blue-600 text-sm font-medium">Selected</span> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const BackendSelector: React.FC = () => { | ||
| const { state, selectBackend } = useSettings(); | ||
|
|
||
| const llmOptions = state.backendOptions.filter(option => option.type === 'LLM'); | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
llm is not a recognized word. (unrecognized-spelling)
|
||
| const slmOptions = state.backendOptions.filter(option => option.type === 'SLM'); | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
slm is not a recognized word. (unrecognized-spelling)
Check failureCode scanning / check-spelling Unrecognized Spelling Error
SLM is not a recognized word. (unrecognized-spelling)
|
||
|
|
||
| return ( | ||
| <div className="space-y-6"> | ||
| <div> | ||
| <h2 className="text-2xl font-bold text-gray-900 mb-2">Backend Configuration</h2> | ||
| <p className="text-gray-600"> | ||
| Select the backend infrastructure for your SLM/LLM processing needs. | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
SLM is not a recognized word. (unrecognized-spelling)
|
||
| </p> | ||
| </div> | ||
|
|
||
| {state.selectedBackend && ( | ||
| <div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> | ||
| <div className="flex items-center"> | ||
| <svg className="w-5 h-5 text-blue-500 mr-2" fill="currentColor" viewBox="0 0 20 20"> | ||
| <path | ||
| fillRule="evenodd" | ||
| d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| <span className="text-blue-800 font-medium"> | ||
| Currently selected: {state.backendOptions.find(opt => opt.id === state.selectedBackend)?.name} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| <div> | ||
| <h3 className="text-xl font-semibold text-gray-900 mb-4">Large Language Models (LLM)</h3> | ||
| <div className="grid gap-4 md:grid-cols-2"> | ||
| {llmOptions.map((option) => ( | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
llm is not a recognized word. (unrecognized-spelling)
|
||
| <BackendCard | ||
| key={option.id} | ||
| option={option} | ||
| isSelected={state.selectedBackend === option.id} | ||
| onSelect={() => selectBackend(option.id)} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h3 className="text-xl font-semibold text-gray-900 mb-4">Small Language Models (SLM)</h3> | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
SLM is not a recognized word. (unrecognized-spelling)
|
||
| <div className="grid gap-4 md:grid-cols-2"> | ||
| {slmOptions.map((option) => ( | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
slm is not a recognized word. (unrecognized-spelling)
|
||
| <BackendCard | ||
| key={option.id} | ||
| option={option} | ||
| isSelected={state.selectedBackend === option.id} | ||
| onSelect={() => selectBackend(option.id)} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Frontend - SLM/LLM Infrastructure</title> | ||
Check failureCode scanning / check-spelling Unrecognized Spelling Error
SLM is not a recognized word. (unrecognized-spelling)
|
||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/main.tsx"></script> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import React from 'react' | ||
| import ReactDOM from 'react-dom/client' | ||
| import App from './App' | ||
|
|
||
| ReactDOM.createRoot(document.getElementById('root')!).render( | ||
| <React.StrictMode> | ||
| <App /> | ||
| </React.StrictMode>, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import React from 'react'; | ||
|
|
||
| export const DashboardPage: React.FC = () => { | ||
| return ( | ||
| <div className="min-h-screen bg-gray-50"> | ||
| <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> | ||
| <div className="bg-white shadow rounded-lg"> | ||
| <div className="px-6 py-8"> | ||
| <h1 className="text-3xl font-bold text-gray-900 mb-4">Dashboard</h1> | ||
| <p className="text-gray-600 mb-6"> | ||
| Welcome to the SLM/LLM Infrastructure Frontend. Use the settings page to configure your backend. | ||
| </p> | ||
|
|
||
| <div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> | ||
| <div className="flex items-center"> | ||
| <svg className="w-5 h-5 text-blue-500 mr-2" fill="currentColor" viewBox="0 0 20 20"> | ||
| <path | ||
| fillRule="evenodd" | ||
| d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" | ||
| clipRule="evenodd" | ||
| /> | ||
| </svg> | ||
| <span className="text-blue-800 font-medium"> | ||
| Navigate to Settings to configure your preferred backend infrastructure. | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import React from 'react'; | ||
| import { BackendSelector } from '../features/settings/BackendSelector'; | ||
|
|
||
| export const SettingsPage: React.FC = () => { | ||
| return ( | ||
| <div className="min-h-screen bg-gray-50"> | ||
| <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> | ||
| <div className="bg-white shadow rounded-lg"> | ||
| <div className="px-6 py-8"> | ||
| <div className="border-b border-gray-200 pb-6 mb-6"> | ||
| <h1 className="text-3xl font-bold text-gray-900">Settings</h1> | ||
| <p className="mt-2 text-gray-600"> | ||
| Configure your application preferences and backend infrastructure. | ||
| </p> | ||
| </div> | ||
|
|
||
| <BackendSelector /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using window.location.reload() for hash changes causes unnecessary full page reloads. This defeats the purpose of client-side routing and impacts performance. Use React state to trigger re-renders instead.