Manage Onboarding decision via DB flag (#4926)

* WIP add onboarding flag to db

* dev build

* fix onboarding telem call
This commit is contained in:
Timothy Carambat 2026-01-28 16:32:26 -08:00 committed by GitHub
parent 88459ce2d2
commit 54e0cde56f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 160 additions and 21 deletions

View File

@ -6,7 +6,7 @@ concurrency:
on:
push:
branches: ['4855-thinking-block-toggle'] # put your current branch to create a build. Core team only.
branches: ['onboarding-flag'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'

View File

@ -19,27 +19,18 @@ function useIsAuthenticated() {
useEffect(() => {
const validateSession = async () => {
const {
MultiUserMode,
RequiresAuth,
LLMProvider = null,
VectorDB = null,
} = await System.keys();
const onboardingComplete = await System.isOnboardingComplete();
const { MultiUserMode, RequiresAuth } = await System.keys();
setMultiUserMode(MultiUserMode);
// Check for the onboarding redirect condition
if (
!MultiUserMode &&
!RequiresAuth && // Not in Multi-user AND no password set.
!LLMProvider &&
!VectorDB
) {
if (onboardingComplete === false) {
setShouldRedirectToOnboarding(true);
setIsAuthed(true);
return;
}
// Single User mode without password - no auth required
if (!MultiUserMode && !RequiresAuth) {
setIsAuthed(true);
return;
@ -58,6 +49,7 @@ function useIsAuthenticated() {
return;
}
// Multi-user mode checks
const localUser = localStorage.getItem(AUTH_USER);
const localAuthToken = localStorage.getItem(AUTH_TOKEN);
if (!localUser || !localAuthToken) {

View File

@ -0,0 +1,16 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import System from "@/models/system";
import paths from "@/utils/paths";
export default function useRedirectToHomeOnOnboardingComplete() {
const navigate = useNavigate();
useEffect(() => {
async function checkOnboardingComplete() {
const onboardingComplete = await System.isOnboardingComplete();
if (onboardingComplete === false) return;
navigate(paths.home());
}
checkOnboardingComplete();
}, []);
}

View File

@ -32,6 +32,32 @@ const System = {
.then((res) => res.vectorCount)
.catch(() => 0);
},
/**
* Checks if the onboarding is complete.
* @returns {Promise<boolean>}
*/
isOnboardingComplete: async function () {
return await fetch(`${API_BASE}/onboarding`)
.then((res) => {
if (!res.ok) throw new Error("Could not find onboarding information.");
return res.json();
})
.then((res) => res.onboardingComplete)
.catch(() => false);
},
/**
* Marks the onboarding as complete.
* @returns {Promise<boolean>}
*/
markOnboardingComplete: async function () {
return await fetch(`${API_BASE}/onboarding`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.ok)
.catch(() => false);
},
keys: async function () {
return await fetch(`${API_BASE}/setup-complete`)
.then((res) => {

View File

@ -7,6 +7,7 @@ import AnythingLLMLogo from "@/media/logo/anything-llm.png";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@/hooks/useTheme";
import { useTranslation } from "react-i18next";
import useRedirectToHomeOnOnboardingComplete from "@/hooks/useOnboardingComplete";
const IMG_SRCSET = {
light: {
@ -21,6 +22,7 @@ const IMG_SRCSET = {
export default function OnboardingHome() {
const navigate = useNavigate();
useRedirectToHomeOnOnboardingComplete();
const { theme } = useTheme();
const { t } = useTranslation();
const srcSet = IMG_SRCSET?.[theme] || IMG_SRCSET.default;

View File

@ -337,9 +337,16 @@ export default function LLMPreference({
fetchKeys();
}, []);
function handleForward() {
if (hiddenSubmitButtonRef.current) {
hiddenSubmitButtonRef.current.click();
async function handleForward() {
try {
await System.markOnboardingComplete();
console.log("Onboarding complete");
} catch (error) {
console.error("Onboarding complete failed", error);
} finally {
if (hiddenSubmitButtonRef.current) {
hiddenSubmitButtonRef.current.click();
}
}
}

View File

@ -1,6 +1,7 @@
import { ArrowLeft, ArrowRight } from "@phosphor-icons/react";
import { useState } from "react";
import { isMobile } from "react-device-detect";
import useRedirectToHomeOnOnboardingComplete from "@/hooks/useOnboardingComplete";
import Home from "./Home";
import LLMPreference from "./LLMPreference";
import UserSetup from "./UserSetup";
@ -18,6 +19,7 @@ const OnboardingSteps = {
export default OnboardingSteps;
export function OnboardingLayout({ children }) {
useRedirectToHomeOnOnboardingComplete();
const [header, setHeader] = useState({
title: "",
description: "",

View File

@ -80,6 +80,26 @@ function systemEndpoints(app) {
response.sendStatus(200).end();
});
app.get("/onboarding", async (_, response) => {
try {
const results = await SystemSettings.isOnboardingComplete();
response.status(200).json({ onboardingComplete: results });
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
});
app.post("/onboarding", [validatedRequest], async (_, response) => {
try {
await SystemSettings.markOnboardingComplete();
response.sendStatus(200).end();
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
});
app.get("/setup-complete", async (_, response) => {
try {
const results = await SystemSettings.currentSettings();

View File

@ -71,9 +71,6 @@ function workspaceEndpoints(app) {
},
user?.id
);
if (onboardingComplete === true)
await Telemetry.sendTelemetry("onboarding_complete");
response.status(200).json({ workspace, message });
} catch (e) {
console.error(e.message, e);

View File

@ -20,7 +20,7 @@ const SystemSettings = {
/** A default system prompt that is used when no other system prompt is set or available to the function caller. */
saneDefaultSystemPrompt:
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.",
protectedFields: ["multi_user_mode", "hub_api_key"],
protectedFields: ["multi_user_mode", "hub_api_key", "onboarding_complete"],
publicFields: [
"footer_data",
"support_email",
@ -411,6 +411,28 @@ const SystemSettings = {
}
},
isOnboardingComplete: async function () {
try {
const setting = await this.get({ label: "onboarding_complete" });
return setting?.value === "true";
} catch (error) {
console.error(error.message);
return false;
}
},
markOnboardingComplete: async function () {
try {
await this._updateSettings({ onboarding_complete: true });
const { Telemetry } = require("./telemetry");
await Telemetry.sendTelemetry("onboarding_complete");
return true;
} catch (error) {
console.error(error.message);
return false;
}
},
currentLogoFilename: async function () {
try {
const setting = await this.get({ label: "logo_filename" });

View File

@ -4,6 +4,7 @@ const { EncryptionManager } = require("../EncryptionManager");
const { CommunicationKey } = require("../comKey");
const setupTelemetry = require("../telemetry");
const eagerLoadContextWindows = require("./eagerLoadContextWindows");
const markOnboarded = require("./markOnboarded");
// Testing SSL? You can make a self signed certificate and point the ENVs to that location
// make a directory in server called 'sslcert' - cd into it
@ -28,6 +29,7 @@ function bootSSL(app, port = 3001) {
server
.listen(port, async () => {
await markOnboarded();
await setupTelemetry();
new CommunicationKey(true);
new EncryptionManager();
@ -58,6 +60,7 @@ function bootHTTP(app, port = 3001) {
app
.listen(port, async () => {
await markOnboarded();
await setupTelemetry();
new CommunicationKey(true);
new EncryptionManager();

View File

@ -0,0 +1,52 @@
const { SystemSettings } = require("../../models/systemSettings");
/**
* Mark the onboarding as completed for legacy users prior to this change where onboarding is now a flag in the DB.
* This is a legacy patch to ensure that existing users are not redirected to the onboarding page who have been using the app for a while.
*/
async function markOnboarded() {
try {
const onboardingStatus = await SystemSettings.isOnboardingComplete();
if (onboardingStatus === true) return;
// Check if the server is already onboarded by the old way of checking if the server in any way has been setup.
// If it is, then we can mark the onboarding as complete in the DB to persist this
const alreadyOnboarded = await isLegacyOnboarded();
if (alreadyOnboarded === true) {
console.log(
"\x1b[33m[ONBOARDING PATCH]\x1b[0m Legacy instance is already onboarded, marking onboarding as complete. You will not see this message again."
);
await SystemSettings.markOnboardingComplete();
return true;
}
return false;
} catch (e) {
console.error(
"\x1b[31m[ONBOARDING PATCH]\x1b[0m Error marking onboarding as complete",
e.message,
e
);
return false;
}
}
/**
* Check if the server is already onboarded by the old way of checking if the server in any way has been setup.
* @returns {Promise<boolean>}
*/
async function isLegacyOnboarded() {
// LLM Provider is set, so we can assume onboarding is complete since this is default null in SystemSettings.js
if (!!process.env.LLM_PROVIDER) return true;
// Vector DB is set, so we can assume onboarding is complete since this is default null in SystemSettings.js (default is lancedb in frontend)
if (!!process.env.VECTOR_DB) return true;
// Check if the AUTH_TOKEN/JWT_SECRET is set, so we can assume onboarding is complete since this is default null in SystemSettings.js
if (!!process.env.AUTH_TOKEN || !!process.env.JWT_SECRET) return true;
// Check multi-user mode is enabled, if it is, then they are already using the app.
if ((await SystemSettings.isMultiUserMode()) === true) return true;
return false;
}
module.exports = markOnboarded;