From 89393255d6f5f89486ed1e7590337f203ce051e9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:29:23 +0000 Subject: [PATCH 1/2] Migrate UI to React with comprehensive features - Created React app with TypeScript, Tailwind CSS, and shadcn/ui - Implemented all existing UI features: - Model status display with real-time updates - Single and multi-model query support - Model version selection for each provider - Response display with multiple view modes (Grid, Side-by-Side, Stacked) - Copy to clipboard functionality - Download responses in TXT, PDF, and DOCX formats - Theme toggle (light/dark mode) - Responsive design with mobile support - Added missing features: - History page with query history, search, and filtering - Settings page with API configuration options - About page with system information and supported models - React Router for navigation between pages - Context providers for theme and history management - Local storage for history persistence - Updated Go server to serve React build from frontend/dist - All lint checks passing (only warnings from pre-installed shadcn/ui components) - Updated model versions to include latest models (Gemini 2.0 Flash, Claude 3.5 Sonnet, GPT-4o) Co-Authored-By: samorin7@gmail.com --- cmd/server/main.go | 11 +- frontend/.env.example | 1 + frontend/.gitignore | 24 + frontend/README.md | 50 ++ frontend/components.json | 21 + frontend/eslint.config.js | 28 + frontend/index.html | 13 + frontend/postcss.config.js | 6 + frontend/public/vite.svg | 1 + frontend/src/App.css | 7 + frontend/src/App.tsx | 30 + frontend/src/assets/react.svg | 1 + .../src/components/Dashboard/ModelStatus.tsx | 76 ++ .../src/components/Dashboard/QueryForm.tsx | 233 ++++++ .../components/Dashboard/ResponseDisplay.tsx | 300 +++++++ frontend/src/components/Layout/Layout.tsx | 21 + frontend/src/components/Layout/Sidebar.tsx | 98 +++ frontend/src/components/ui/accordion.tsx | 55 ++ frontend/src/components/ui/alert-dialog.tsx | 139 ++++ frontend/src/components/ui/alert.tsx | 59 ++ frontend/src/components/ui/aspect-ratio.tsx | 5 + frontend/src/components/ui/avatar.tsx | 50 ++ frontend/src/components/ui/badge.tsx | 36 + frontend/src/components/ui/breadcrumb.tsx | 115 +++ frontend/src/components/ui/button-group.tsx | 83 ++ frontend/src/components/ui/button.tsx | 57 ++ frontend/src/components/ui/card.tsx | 76 ++ frontend/src/components/ui/carousel.tsx | 260 ++++++ frontend/src/components/ui/checkbox.tsx | 28 + frontend/src/components/ui/collapsible.tsx | 11 + frontend/src/components/ui/command.tsx | 151 ++++ frontend/src/components/ui/context-menu.tsx | 198 +++++ frontend/src/components/ui/dialog.tsx | 122 +++ frontend/src/components/ui/drawer.tsx | 118 +++ frontend/src/components/ui/dropdown-menu.tsx | 199 +++++ frontend/src/components/ui/empty.tsx | 104 +++ frontend/src/components/ui/field.tsx | 244 ++++++ frontend/src/components/ui/form.tsx | 178 ++++ frontend/src/components/ui/hover-card.tsx | 27 + frontend/src/components/ui/input-group.tsx | 168 ++++ frontend/src/components/ui/input-otp.tsx | 69 ++ frontend/src/components/ui/input.tsx | 22 + frontend/src/components/ui/item.tsx | 193 +++++ frontend/src/components/ui/kbd.tsx | 28 + frontend/src/components/ui/label.tsx | 24 + frontend/src/components/ui/menubar.tsx | 256 ++++++ .../src/components/ui/navigation-menu.tsx | 128 +++ frontend/src/components/ui/pagination.tsx | 117 +++ frontend/src/components/ui/popover.tsx | 31 + frontend/src/components/ui/progress.tsx | 28 + frontend/src/components/ui/radio-group.tsx | 42 + frontend/src/components/ui/resizable.tsx | 45 + frontend/src/components/ui/scroll-area.tsx | 46 ++ frontend/src/components/ui/select.tsx | 159 ++++ frontend/src/components/ui/separator.tsx | 29 + frontend/src/components/ui/sheet.tsx | 138 ++++ frontend/src/components/ui/sidebar.tsx | 773 ++++++++++++++++++ frontend/src/components/ui/skeleton.tsx | 15 + frontend/src/components/ui/slider.tsx | 28 + frontend/src/components/ui/sonner.tsx | 29 + frontend/src/components/ui/spinner.tsx | 16 + frontend/src/components/ui/switch.tsx | 29 + frontend/src/components/ui/table.tsx | 120 +++ frontend/src/components/ui/tabs.tsx | 53 ++ frontend/src/components/ui/textarea.tsx | 22 + frontend/src/components/ui/toast.tsx | 129 +++ frontend/src/components/ui/toaster.tsx | 35 + frontend/src/components/ui/toggle-group.tsx | 61 ++ frontend/src/components/ui/toggle.tsx | 43 + frontend/src/components/ui/tooltip.tsx | 30 + frontend/src/contexts/HistoryContext.tsx | 71 ++ frontend/src/contexts/ThemeContext.tsx | 41 + frontend/src/hooks/use-mobile.tsx | 19 + frontend/src/hooks/use-toast.ts | 189 +++++ frontend/src/index.css | 27 + frontend/src/lib/utils.ts | 6 + frontend/src/main.tsx | 10 + frontend/src/pages/About.tsx | 187 +++++ frontend/src/pages/Dashboard.tsx | 29 + frontend/src/pages/History.tsx | 160 ++++ frontend/src/pages/Settings.tsx | 150 ++++ frontend/src/types/index.ts | 50 ++ frontend/src/utils/api.ts | 65 ++ frontend/src/utils/constants.ts | 40 + frontend/src/utils/download.ts | 123 +++ frontend/src/vite-env.d.ts | 1 + frontend/tailwind.config.js | 50 ++ frontend/tsconfig.app.json | 33 + frontend/tsconfig.json | 18 + frontend/tsconfig.node.json | 24 + frontend/vite.config.ts | 13 + 91 files changed, 7445 insertions(+), 3 deletions(-) create mode 100644 frontend/.env.example create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/components.json create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/postcss.config.js create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/Dashboard/ModelStatus.tsx create mode 100644 frontend/src/components/Dashboard/QueryForm.tsx create mode 100644 frontend/src/components/Dashboard/ResponseDisplay.tsx create mode 100644 frontend/src/components/Layout/Layout.tsx create mode 100644 frontend/src/components/Layout/Sidebar.tsx create mode 100644 frontend/src/components/ui/accordion.tsx create mode 100644 frontend/src/components/ui/alert-dialog.tsx create mode 100644 frontend/src/components/ui/alert.tsx create mode 100644 frontend/src/components/ui/aspect-ratio.tsx create mode 100644 frontend/src/components/ui/avatar.tsx create mode 100644 frontend/src/components/ui/badge.tsx create mode 100644 frontend/src/components/ui/breadcrumb.tsx create mode 100644 frontend/src/components/ui/button-group.tsx create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/components/ui/card.tsx create mode 100644 frontend/src/components/ui/carousel.tsx create mode 100644 frontend/src/components/ui/checkbox.tsx create mode 100644 frontend/src/components/ui/collapsible.tsx create mode 100644 frontend/src/components/ui/command.tsx create mode 100644 frontend/src/components/ui/context-menu.tsx create mode 100644 frontend/src/components/ui/dialog.tsx create mode 100644 frontend/src/components/ui/drawer.tsx create mode 100644 frontend/src/components/ui/dropdown-menu.tsx create mode 100644 frontend/src/components/ui/empty.tsx create mode 100644 frontend/src/components/ui/field.tsx create mode 100644 frontend/src/components/ui/form.tsx create mode 100644 frontend/src/components/ui/hover-card.tsx create mode 100644 frontend/src/components/ui/input-group.tsx create mode 100644 frontend/src/components/ui/input-otp.tsx create mode 100644 frontend/src/components/ui/input.tsx create mode 100644 frontend/src/components/ui/item.tsx create mode 100644 frontend/src/components/ui/kbd.tsx create mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/components/ui/menubar.tsx create mode 100644 frontend/src/components/ui/navigation-menu.tsx create mode 100644 frontend/src/components/ui/pagination.tsx create mode 100644 frontend/src/components/ui/popover.tsx create mode 100644 frontend/src/components/ui/progress.tsx create mode 100644 frontend/src/components/ui/radio-group.tsx create mode 100644 frontend/src/components/ui/resizable.tsx create mode 100644 frontend/src/components/ui/scroll-area.tsx create mode 100644 frontend/src/components/ui/select.tsx create mode 100644 frontend/src/components/ui/separator.tsx create mode 100644 frontend/src/components/ui/sheet.tsx create mode 100644 frontend/src/components/ui/sidebar.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/ui/slider.tsx create mode 100644 frontend/src/components/ui/sonner.tsx create mode 100644 frontend/src/components/ui/spinner.tsx create mode 100644 frontend/src/components/ui/switch.tsx create mode 100644 frontend/src/components/ui/table.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/components/ui/textarea.tsx create mode 100644 frontend/src/components/ui/toast.tsx create mode 100644 frontend/src/components/ui/toaster.tsx create mode 100644 frontend/src/components/ui/toggle-group.tsx create mode 100644 frontend/src/components/ui/toggle.tsx create mode 100644 frontend/src/components/ui/tooltip.tsx create mode 100644 frontend/src/contexts/HistoryContext.tsx create mode 100644 frontend/src/contexts/ThemeContext.tsx create mode 100644 frontend/src/hooks/use-mobile.tsx create mode 100644 frontend/src/hooks/use-toast.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/lib/utils.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/About.tsx create mode 100644 frontend/src/pages/Dashboard.tsx create mode 100644 frontend/src/pages/History.tsx create mode 100644 frontend/src/pages/Settings.tsx create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/src/utils/api.ts create mode 100644 frontend/src/utils/constants.ts create mode 100644 frontend/src/utils/download.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts diff --git a/cmd/server/main.go b/cmd/server/main.go index 346a3d2..5cdfd67 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -63,10 +63,15 @@ func main() { v1Router := r.PathPrefix("/v1/gateway").Subrouter() v1.SetupV1Routes(v1Router, routerInstance, catalogLoader, jobStore, jobWorker) - r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./ui")))) + r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("./frontend/dist/assets")))) - r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, filepath.Join("ui", "templates", "index.html")) + r.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := filepath.Join("frontend", "dist", r.URL.Path) + if _, err := os.Stat(path); err == nil { + http.ServeFile(w, r, path) + return + } + http.ServeFile(w, r, filepath.Join("frontend", "dist", "index.html")) }) port := cfg.Port diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..460e575 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:8080 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..78cd18f --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..072a57e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..f34ac70 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,7 @@ +#root { + max-width: none; + width: 100%; + margin: 0; + padding: 0; + text-align: left; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..192c35c --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,30 @@ +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { ThemeProvider } from './contexts/ThemeContext'; +import { HistoryProvider } from './contexts/HistoryContext'; +import Layout from './components/Layout/Layout'; +import Dashboard from './pages/Dashboard'; +import History from './pages/History'; +import Settings from './pages/Settings'; +import About from './pages/About'; +import './App.css'; + +function App() { + return ( + + + + + + } /> + } /> + } /> + } /> + + + + + + ); +} + +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Dashboard/ModelStatus.tsx b/frontend/src/components/Dashboard/ModelStatus.tsx new file mode 100644 index 0000000..8c86942 --- /dev/null +++ b/frontend/src/components/Dashboard/ModelStatus.tsx @@ -0,0 +1,76 @@ +import React, { useEffect, useState } from 'react'; +import { api } from '../../utils/api'; +import { ModelStatus as ModelStatusType } from '../../types'; +import { MODEL_ICONS } from '../../utils/constants'; +import { Loader2 } from 'lucide-react'; + +const ModelStatus: React.FC = () => { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchStatus = async () => { + try { + setLoading(true); + setError(null); + const data = await api.fetchStatus(); + setStatus(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch status'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchStatus(); + const interval = setInterval(fetchStatus, 30000); + return () => clearInterval(interval); + }, []); + + if (loading && !status) { + return ( +
+

Model Status

+
+ +
+
+ ); + } + + if (error) { + return ( +
+

Model Status

+
{error}
+
+ ); + } + + return ( +
+

Model Status

+
+ {status && Object.entries(status).map(([model, available]) => ( +
+
+ {MODEL_ICONS[model as keyof typeof MODEL_ICONS] || '🤖'} +
+
{model}
+
{available ? 'Available' : 'Unavailable'}
+
+ ))} +
+
+ ); +}; + +export default ModelStatus; diff --git a/frontend/src/components/Dashboard/QueryForm.tsx b/frontend/src/components/Dashboard/QueryForm.tsx new file mode 100644 index 0000000..6812e99 --- /dev/null +++ b/frontend/src/components/Dashboard/QueryForm.tsx @@ -0,0 +1,233 @@ +import React, { useState } from 'react'; +import { api, generateRequestId } from '../../utils/api'; +import { SingleModelResponse, MultiModelResponse } from '../../types'; +import { MODEL_VERSIONS, TASK_TYPES } from '../../utils/constants'; +import { Loader2 } from 'lucide-react'; +import { useHistory } from '../../contexts/HistoryContext'; + +interface QueryFormProps { + onResponse: (response: SingleModelResponse | MultiModelResponse, isMultiModel: boolean) => void; +} + +const QueryForm: React.FC = ({ onResponse }) => { + const [query, setQuery] = useState(''); + const [taskType, setTaskType] = useState(''); + const [selectedModels, setSelectedModels] = useState<{ [key: string]: boolean }>({ + openai: true, + gemini: true, + mistral: true, + claude: true, + }); + const [modelVersions, setModelVersions] = useState<{ [key: string]: string }>({ + openai: 'gpt-3.5-turbo', + gemini: 'gemini-pro', + mistral: 'mistral-small', + claude: 'claude-3-sonnet-20240229', + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const { addToHistory } = useHistory(); + + const selectedModelsList = Object.entries(selectedModels) + .filter(([, selected]) => selected) + .map(([model]) => model); + + const handleModelToggle = (model: string) => { + setSelectedModels(prev => ({ ...prev, [model]: !prev[model] })); + }; + + const handleVersionChange = (model: string, version: string) => { + setModelVersions(prev => ({ ...prev, [model]: version })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!query.trim()) { + setError('Please enter a query'); + return; + } + + if (selectedModelsList.length === 0) { + setError('Please select at least one model'); + return; + } + + setLoading(true); + setError(null); + + try { + const isMultiModel = selectedModelsList.length > 1; + const requestId = generateRequestId(); + + if (isMultiModel) { + const selectedVersions: { [key: string]: string } = {}; + selectedModelsList.forEach(model => { + selectedVersions[model] = modelVersions[model]; + }); + + const request = { + query, + models: selectedModelsList, + task_type: taskType || undefined, + request_id: requestId, + model_versions: selectedVersions, + }; + + const response = await api.submitParallelQuery(request); + onResponse(response, true); + + addToHistory({ + query, + models: selectedModelsList, + taskType: taskType || undefined, + response: JSON.stringify(response.responses), + responseTime: response.elapsed_time_ms, + cached: false, + }); + } else { + const model = selectedModelsList[0]; + const request = { + query, + model, + task_type: taskType || undefined, + request_id: requestId, + model_version: modelVersions[model], + }; + + const response = await api.submitQuery(request); + onResponse(response, false); + + addToHistory({ + query, + model, + taskType: taskType || undefined, + response: response.response, + responseTime: response.response_time_ms, + cached: response.cached, + tokens: response.total_tokens || response.num_tokens, + }); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to submit query'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Query LLM

+ + {error && ( +
+ {error} +
+ )} + +
+ {/* Model Selection */} +
+ +
+
+ {Object.keys(selectedModels).map((model) => ( +
+ handleModelToggle(model)} + className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500" + /> + + {selectedModels[model] && ( + + )} +
+ ))} +
+
+ 1 + ? 'bg-blue-600 text-white' + : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300' + }`}> + {selectedModelsList.length} selected + + + Select multiple models to compare responses + +
+
+
+ + {/* Task Type */} +
+ + +
+ + {/* Query Input */} +
+ +