diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e7ccf85..749047b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -205,7 +205,6 @@ deploy-rag:
ENDSSH
deploy-gateway:
- <<: *deploy_setup
<<: *deploy_setup
needs: [publish-gateway]
script:
diff --git a/rag-view/.gitignore b/rag-view/.gitignore
new file mode 100644
index 0000000..0760178
--- /dev/null
+++ b/rag-view/.gitignore
@@ -0,0 +1,26 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+.env
+
+# Editor directories and files
+.vscode/*
+.git
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/rag-view/README.md b/rag-view/README.md
new file mode 100644
index 0000000..7059a96
--- /dev/null
+++ b/rag-view/README.md
@@ -0,0 +1,12 @@
+# React + 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) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/rag-view/docker/Dockerfile b/rag-view/docker/Dockerfile
new file mode 100644
index 0000000..26d99eb
--- /dev/null
+++ b/rag-view/docker/Dockerfile
@@ -0,0 +1,11 @@
+FROM node:22-alpine AS build
+WORKDIR /app
+COPY package.json package-lock.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM nginx:alpine
+COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
+COPY --from=build /app/dist /usr/share/nginx/html/ragview
+EXPOSE 80
\ No newline at end of file
diff --git a/rag-view/docker/nginx.conf b/rag-view/docker/nginx.conf
new file mode 100644
index 0000000..49f01dd
--- /dev/null
+++ b/rag-view/docker/nginx.conf
@@ -0,0 +1,8 @@
+server {
+ listen 80;
+ root /usr/share/nginx/html;
+
+ location /ragview/ {
+ try_files $uri $uri/ /ragview/index.html;
+ }
+}
\ No newline at end of file
diff --git a/rag-view/eslint.config.js b/rag-view/eslint.config.js
new file mode 100644
index 0000000..cee1e2c
--- /dev/null
+++ b/rag-view/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/rag-view/index.html b/rag-view/index.html
new file mode 100644
index 0000000..7ad8f9c
--- /dev/null
+++ b/rag-view/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Upload text files for context searching in LLM
+
+
+ Each file must be no more than {MAX_FILE_SIZE_KB} KB. You can upload up
+ to {MAX_FILES_TO_UPLOAD} files at once.
+
+
+ {/* File quota info */}
+
+
+ Files loaded:
+
+ {loadedCount} / {maxFilesToLoad}
+
+
+
+ Remaining slots:
+ 0 ? "#38a169" : "#e53e3e",
+ }}
+ >
+ {remainingSlots}
+
+
+
+
0 ? (loadedCount / maxFilesToLoad) * 100 : 0
+ }%`,
+ }}
+ />
+
+
+
+ {!areFilesValid && !quotaError && (
+
+ ⚠️
+ File requirements not met. Please adjust your selection.
+
+ )}
+
+ {quotaError && (
+
+ ⚠️
+ {quotaError}
+
+ )}
+
+ {remainingSlots === 0 && !quotaError && (
+
+
🚫
+
+
+ You have reached the maximum number of files
+
+
+
+ )}
+
+
+
+
+
+ {selectedFiles.length > 0 && (
+
+
+ Selected files ({selectedFiles.length}):
+
+
+ {selectedFiles.map((file, index) => (
+ -
+ 📄
+ {file.name}
+
+ {(file.size / 1024).toFixed(1)} KB
+
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+const styles = {
+ container: {
+ padding: "2rem",
+ maxWidth: "600px",
+ margin: "0 auto",
+ fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
+ },
+ title: {
+ fontSize: "1.5rem",
+ fontWeight: 600,
+ color: "#1a1a2e",
+ marginBottom: "0.5rem",
+ },
+ subtitle: {
+ color: "#666",
+ marginBottom: "1.5rem",
+ lineHeight: 1.5,
+ },
+ quotaContainer: {
+ backgroundColor: "#f8f9fa",
+ borderRadius: "8px",
+ padding: "1rem",
+ marginBottom: "1rem",
+ border: "1px solid #e0e0e0",
+ },
+ quotaRow: {
+ display: "flex",
+ justifyContent: "space-between",
+ marginBottom: "0.5rem",
+ },
+ quotaLabel: {
+ color: "#666",
+ fontSize: "0.9rem",
+ },
+ quotaValue: {
+ fontWeight: 500,
+ color: "#1a1a2e",
+ },
+ quotaProgressContainer: {
+ width: "100%",
+ height: "6px",
+ backgroundColor: "#e0e0e0",
+ borderRadius: "3px",
+ overflow: "hidden",
+ marginTop: "0.5rem",
+ },
+ quotaProgressFill: {
+ height: "100%",
+ backgroundColor: "#667eea",
+ borderRadius: "3px",
+ transition: "width 0.3s ease",
+ },
+ progressContainer: {
+ backgroundColor: "#f8f9fa",
+ borderRadius: "12px",
+ padding: "1.5rem",
+ marginTop: "1.5rem",
+ },
+ progressHeader: {
+ display: "flex",
+ alignItems: "center",
+ gap: "0.5rem",
+ fontSize: "1.1rem",
+ fontWeight: 500,
+ marginBottom: "1rem",
+ },
+ progressIcon: {
+ fontSize: "1.5rem",
+ },
+ progressBarContainer: {
+ width: "100%",
+ height: "8px",
+ backgroundColor: "#e0e0e0",
+ borderRadius: "4px",
+ overflow: "hidden",
+ marginBottom: "1rem",
+ },
+ progressBarFill: {
+ height: "100%",
+ backgroundColor: "#4caf50",
+ borderRadius: "4px",
+ transition: "width 0.3s ease",
+ },
+ progressStats: {
+ display: "flex",
+ justifyContent: "center",
+ marginBottom: "1rem",
+ },
+ statItem: {
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ },
+ statValue: {
+ fontSize: "1.5rem",
+ fontWeight: 600,
+ color: "#1a1a2e",
+ },
+ statLabel: {
+ fontSize: "0.85rem",
+ color: "#666",
+ },
+ currentFile: {
+ display: "flex",
+ alignItems: "center",
+ gap: "0.75rem",
+ padding: "0.75rem",
+ backgroundColor: "#fff",
+ borderRadius: "8px",
+ border: "1px solid #e0e0e0",
+ },
+ currentFileName: {
+ flex: 1,
+ fontWeight: 500,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ },
+ currentFileStatus: {
+ fontSize: "0.85rem",
+ color: "#666",
+ },
+ cancelButtonContainer: {
+ display: "flex",
+ justifyContent: "center",
+ marginTop: "1.5rem",
+ },
+ cancelButton: {
+ padding: "0.75rem 2rem",
+ fontSize: "1rem",
+ fontWeight: 500,
+ color: "#fff",
+ backgroundColor: "#e53e3e",
+ border: "none",
+ borderRadius: "6px",
+ cursor: "pointer",
+ transition: "background-color 0.2s",
+ },
+ errorBox: {
+ display: "flex",
+ alignItems: "center",
+ gap: "1rem",
+ padding: "1rem",
+ backgroundColor: "#fff5f5",
+ border: "1px solid #feb2b2",
+ borderRadius: "8px",
+ marginTop: "1rem",
+ marginBottom: "1rem",
+ },
+ errorIcon: {
+ fontSize: "2rem",
+ },
+ errorMessage: {
+ color: "#c53030",
+ fontWeight: 500,
+ },
+ errorStatus: {
+ color: "#666",
+ fontSize: "0.85rem",
+ },
+ successBox: {
+ display: "flex",
+ alignItems: "center",
+ gap: "1rem",
+ padding: "1rem",
+ backgroundColor: "#f0fff4",
+ border: "1px solid #9ae6b4",
+ borderRadius: "8px",
+ marginTop: "1rem",
+ },
+ successIcon: {
+ fontSize: "2rem",
+ },
+ successMessage: {
+ color: "#276749",
+ fontWeight: 500,
+ fontSize: "1.1rem",
+ },
+ successStats: {
+ color: "#666",
+ fontSize: "0.9rem",
+ },
+ warningBox: {
+ display: "flex",
+ alignItems: "center",
+ gap: "0.5rem",
+ padding: "0.75rem 1rem",
+ backgroundColor: "#fffaf0",
+ border: "1px solid #fbd38d",
+ borderRadius: "8px",
+ color: "#c05621",
+ marginBottom: "1rem",
+ },
+ uploadButton: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: "0.5rem",
+ width: "100%",
+ padding: "1rem",
+ fontSize: "1rem",
+ backgroundColor: "#f8f9fa",
+ border: "2px dashed #ccc",
+ borderRadius: "8px",
+ cursor: "pointer",
+ transition: "all 0.2s",
+ },
+ uploadIcon: {
+ fontSize: "1.25rem",
+ },
+ selectedFilesContainer: {
+ marginTop: "1.5rem",
+ padding: "1rem",
+ backgroundColor: "#f8f9fa",
+ borderRadius: "8px",
+ },
+ selectedFilesTitle: {
+ margin: "0 0 0.75rem 0",
+ fontSize: "1rem",
+ color: "#333",
+ },
+ fileList: {
+ listStyle: "none",
+ margin: 0,
+ padding: 0,
+ },
+ fileItem: {
+ display: "flex",
+ alignItems: "center",
+ gap: "0.5rem",
+ padding: "0.5rem 0",
+ borderBottom: "1px solid #e0e0e0",
+ },
+ fileIcon: {
+ fontSize: "1rem",
+ },
+ fileName: {
+ flex: 1,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ },
+ fileSize: {
+ color: "#666",
+ fontSize: "0.85rem",
+ },
+ buttonGroup: {
+ display: "flex",
+ gap: "0.75rem",
+ marginTop: "1.5rem",
+ },
+ button: {
+ padding: "0.75rem 1.5rem",
+ fontSize: "1rem",
+ fontWeight: 500,
+ color: "#fff",
+ backgroundColor: "#4a5568",
+ border: "none",
+ borderRadius: "6px",
+ cursor: "pointer",
+ transition: "background-color 0.2s",
+ },
+ buttonSecondary: {
+ padding: "0.75rem 1.5rem",
+ fontSize: "1rem",
+ fontWeight: 500,
+ color: "#4a5568",
+ backgroundColor: "#e2e8f0",
+ border: "none",
+ borderRadius: "6px",
+ cursor: "pointer",
+ transition: "background-color 0.2s",
+ },
+ buttonDisabled: {
+ opacity: 0.5,
+ cursor: "not-allowed",
+ },
+};
diff --git a/rag-view/src/store.js b/rag-view/src/store.js
new file mode 100644
index 0000000..b41b09b
--- /dev/null
+++ b/rag-view/src/store.js
@@ -0,0 +1,16 @@
+import { configureStore } from "@reduxjs/toolkit";
+import userDetailsReducer from "./features/slices/details-slice";
+import uploadFilesReducer from "./features/slices/upload-slice";
+import ragSliceReducer from "./features/slices/rag-slice";
+import chatSliceReducer from "./features/slices/chat-slice";
+
+const store = configureStore({
+ reducer: {
+ userDetails: userDetailsReducer,
+ uploadFiles: uploadFilesReducer,
+ ragConfig: ragSliceReducer,
+ chats: chatSliceReducer,
+ },
+});
+
+export default store;
diff --git a/rag-view/vite.config.js b/rag-view/vite.config.js
new file mode 100644
index 0000000..24b61fe
--- /dev/null
+++ b/rag-view/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ base: "/ragview/",
+});