diff --git a/.railwayignore b/.railwayignore new file mode 100644 index 00000000000..d0334307c97 --- /dev/null +++ b/.railwayignore @@ -0,0 +1,18 @@ +node_modules +.git +.env +.env.* +*.log +.DS_Store +.vscode +.idea +README.md +.github +images/ +extras/ +browser-extension/ +embed/ +cloud-deployments/ +docs/ +*.md +!docker/ diff --git a/CHANGES_BY_JONATHANXOXOX.md b/CHANGES_BY_JONATHANXOXOX.md new file mode 100644 index 00000000000..8c46fb6addf --- /dev/null +++ b/CHANGES_BY_JONATHANXOXOX.md @@ -0,0 +1,150 @@ +# Changes by Jonathanxoxox (plain-English summary) + +- PR: Mintplex-Labs/anything-llm #4349 - "Feat/customization" +- PR link: https://github.com/Mintplex-Labs/anything-llm/pull/4349 +- Source branch: Jonathanxoxox:feat/customization +- Date: 2025-09-01 + +## What changed and why + +- Settings access simplified + - Only Admins see and access the full Settings in the app UI. + - Managers can still open manager-allowed pages via direct links. + - Benefit: reduces clutter for non-admins and prevents accidental global changes. + +- Model selection opened up for teams + - All users can choose the AI model for chats/workspaces they can access. + - Server checks were adjusted so regular users can update certain workspace settings and register custom models where appropriate. + - Benefit: teams can try the right model for each task without waiting on an admin. + +- Easier cloud deployment + - Added Railway config and ignore rules. + - Benefit: simpler, cleaner deployments with fewer unnecessary files. + +## What this looks like in the product + +- The Settings button/cog no longer appears for non-admin users. +- The model picker in chat is available to all users for workspaces they can use. +- No data migrations required. + +## Areas touched (non-technical) + +- App navigation: Settings visibility in header/sidebar +- Chat window: model picker control +- Server permissions: who can update a workspace and register custom models +- Deployment: Railway config files + +## Reference commits (friendly descriptions) + +- aa1ec4d... - Restrict Settings to Admin users only (hide Settings for non-admins in UI) +- 82847ca... - Let all users switch AI models; allow default users to update workspaces and custom models +- 08caed6... - Add Railway deployment config (.railwayignore and railway.json) + +For technical details and the full diff, see the PR link above. + +## Changed lines (unified diff) + +### .railwayignore (added) +```diff +@@ + +node_modules + +.git + +.env + +.env.* + +*.log + +.DS_Store + +.vscode + +.idea + +README.md + +.github + +images/ + +extras/ + +browser-extension/ + +embed/ + +cloud-deployments/ + +docs/ + +*.md + +!docker/ +``` + +### railway.json (added) +```diff +@@ + +{ + + "$schema": "https://railway.app/railway.schema.json", + + "build": { + + "builder": "dockerfile", + + "dockerfilePath": "docker/Dockerfile" + + }, + + "deploy": { + + "numReplicas": 1, + + "sleepApplication": false, + + "restartPolicyType": "ON_FAILURE" + + } + +} +``` + +### frontend/src/components/SettingsButton/index.jsx +```diff +@@ -8,7 +8,8 @@ export default function SettingsButton() { + const isInSettings = !!useMatch("/settings/*"); + const { user } = useUser(); + +- if (user && user?.role === "default") return null; ++ // Only show settings button for admin users ++ if (!user || user?.role !== "admin") return null; + + if (isInSettings) + return ( +``` + +### frontend/src/components/Sidebar/index.jsx +```diff +@@ -155,7 +155,7 @@ export function SidebarMobileHeader() { + /> + +- {(!user || user?.role !== "default") && ( ++ {(user && user?.role === "admin") && ( +
+ +
+``` + +### frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action.jsx +```diff +@@ -84,9 +84,8 @@ export default function LLMSelectorAction() { + }, []); + +- // This feature is disabled for multi-user instances where the user is not an admin +- // This is because of the limitations of model selection currently and other nuances in controls. +- if (!!user && user.role !== "admin") return null; ++ // Enable LLM selector for all user roles ++ // Users can change the model for workspaces they have access to + + return ( + <> +``` + +### server/endpoints/system.js +```diff +@@ -966,7 +966,7 @@ function systemEndpoints(app) { + app.post( + "/system/custom-models", +- [validatedRequest, flexUserRoleValid([ROLES.admin])], ++ [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager, ROLES.default])], + async (request, response) => { + try { + const { provider, apiKey = null, basePath = null } = reqBody(request); +``` + +### server/endpoints/workspaces.js +```diff +@@ -84,7 +84,7 @@ function workspaceEndpoints(app) { + app.post( + "/workspace/:slug/update", +- [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], ++ [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager, ROLES.default])], + async (request, response) => { + try { + const user = await userFromSession(request, response); +``` diff --git a/frontend/src/components/SettingsButton/index.jsx b/frontend/src/components/SettingsButton/index.jsx index 97fbefee2d6..3609eec4bd1 100644 --- a/frontend/src/components/SettingsButton/index.jsx +++ b/frontend/src/components/SettingsButton/index.jsx @@ -8,7 +8,8 @@ export default function SettingsButton() { const isInSettings = !!useMatch("/settings/*"); const { user } = useUser(); - if (user && user?.role === "default") return null; + // Only show settings button for admin users + if (!user || user?.role !== "admin") return null; if (isInSettings) return ( diff --git a/frontend/src/components/Sidebar/index.jsx b/frontend/src/components/Sidebar/index.jsx index 1ffe870dff4..cfaa832d30a 100644 --- a/frontend/src/components/Sidebar/index.jsx +++ b/frontend/src/components/Sidebar/index.jsx @@ -155,7 +155,7 @@ export function SidebarMobileHeader() { style={{ objectFit: "contain" }} /> - {(!user || user?.role !== "default") && ( + {(user && user?.role === "admin") && (
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action.jsx index 8988f120942..a9d4da593d9 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/LLMSelector/action.jsx @@ -84,9 +84,8 @@ export default function LLMSelectorAction() { ); }, []); - // This feature is disabled for multi-user instances where the user is not an admin - // This is because of the limitations of model selection currently and other nuances in controls. - if (!!user && user.role !== "admin") return null; + // Enable LLM selector for all user roles + // Users can change the model for workspaces they have access to return ( <> diff --git a/railway.json b/railway.json new file mode 100644 index 00000000000..0c2a24a32c1 --- /dev/null +++ b/railway.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "dockerfile", + "dockerfilePath": "docker/Dockerfile" + }, + "deploy": { + "numReplicas": 1, + "sleepApplication": false, + "restartPolicyType": "ON_FAILURE" + } +} diff --git a/server/endpoints/system.js b/server/endpoints/system.js index fcefd338cc8..17e44014ba1 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -966,7 +966,7 @@ function systemEndpoints(app) { app.post( "/system/custom-models", - [validatedRequest, flexUserRoleValid([ROLES.admin])], + [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager, ROLES.default])], async (request, response) => { try { const { provider, apiKey = null, basePath = null } = reqBody(request); diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index af4eb9983b3..56c34f4cac4 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -84,7 +84,7 @@ function workspaceEndpoints(app) { app.post( "/workspace/:slug/update", - [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], + [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager, ROLES.default])], async (request, response) => { try { const user = await userFromSession(request, response);