Skip to content

Commit

Permalink
Fine tuning order flow (cloud-based) (#1923)
Browse files Browse the repository at this point in the history
* WIP fine-tuning order flow

* flow patches and typo

* Refine steps
add fine tuning CTA on chat page
add fine tuning banner and validation endpoints
add finetuning banner on relevant pages with perms check

* Add prod firebase url
  • Loading branch information
timothycarambat authored Jul 23, 2024
1 parent d0713e1 commit f8e54b2
Show file tree
Hide file tree
Showing 24 changed files with 2,172 additions and 56 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"comkey",
"cooldown",
"cooldowns",
"datafile",
"Deduplicator",
"Dockerized",
"docpath",
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@
"tailwindcss": "^3.3.1",
"vite": "^4.3.0"
}
}
}
6 changes: 6 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const ExperimentalFeatures = lazy(
const LiveDocumentSyncManage = lazy(
() => import("@/pages/Admin/ExperimentalFeatures/Features/LiveSync/manage")
);
const FineTuningWalkthrough = lazy(() => import("@/pages/FineTuning"));

export default function App() {
return (
Expand Down Expand Up @@ -186,6 +187,11 @@ export default function App() {
path="/settings/beta-features/live-document-sync/manage"
element={<AdminRoute Component={LiveDocumentSyncManage} />}
/>

<Route
path="/fine-tuning"
element={<AdminRoute Component={FineTuningWalkthrough} />}
/>
</Routes>
<ToastContainer />
</I18nextProvider>
Expand Down
80 changes: 43 additions & 37 deletions frontend/src/components/SettingsSidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next";
import showToast from "@/utils/toast";
import System from "@/models/system";
import Option from "./MenuOption";
import { FineTuningAlert } from "@/pages/FineTuning/Banner";

export default function SettingsSidebar() {
const { t } = useTranslation();
Expand Down Expand Up @@ -132,48 +133,53 @@ export default function SettingsSidebar() {
}

return (
<div>
<Link
to={paths.home()}
className="flex shrink-0 max-w-[55%] items-center justify-start mx-[38px] my-[18px]"
>
<img
src={logo}
alt="Logo"
className="rounded max-h-[24px]"
style={{ objectFit: "contain" }}
/>
</Link>
<div
ref={sidebarRef}
className="transition-all duration-500 relative m-[16px] rounded-[16px] bg-sidebar border-2 border-outline min-w-[250px] p-[10px] h-[calc(100%-76px)]"
>
<div className="w-full h-full flex flex-col overflow-x-hidden items-between min-w-[235px]">
<div className="text-white text-opacity-60 text-sm font-medium uppercase mt-[4px] mb-0 ml-2">
{t("settings.title")}
</div>
<div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll">
<div className="h-auto sidebar-items">
<div className="flex flex-col gap-y-2 pb-[60px] overflow-y-scroll no-scroll">
<SidebarOptions user={user} t={t} />
<div className="h-[1.5px] bg-[#3D4147] mx-3 mt-[14px]" />
<SupportEmail />
<Link
hidden={user?.hasOwnProperty("role") && user.role !== "admin"}
to={paths.settings.privacy()}
className="text-darker hover:text-white text-xs leading-[18px] mx-3"
>
{t("settings.privacy")}
</Link>
<>
<div>
<Link
to={paths.home()}
className="flex shrink-0 max-w-[55%] items-center justify-start mx-[38px] my-[18px]"
>
<img
src={logo}
alt="Logo"
className="rounded max-h-[24px]"
style={{ objectFit: "contain" }}
/>
</Link>
<div
ref={sidebarRef}
className="transition-all duration-500 relative m-[16px] rounded-[16px] bg-sidebar border-2 border-outline min-w-[250px] p-[10px] h-[calc(100%-76px)]"
>
<div className="w-full h-full flex flex-col overflow-x-hidden items-between min-w-[235px]">
<div className="text-white text-opacity-60 text-sm font-medium uppercase mt-[4px] mb-0 ml-2">
{t("settings.title")}
</div>
<div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll">
<div className="h-auto sidebar-items">
<div className="flex flex-col gap-y-2 pb-[60px] overflow-y-scroll no-scroll">
<SidebarOptions user={user} t={t} />
<div className="h-[1.5px] bg-[#3D4147] mx-3 mt-[14px]" />
<SupportEmail />
<Link
hidden={
user?.hasOwnProperty("role") && user.role !== "admin"
}
to={paths.settings.privacy()}
className="text-darker hover:text-white text-xs leading-[18px] mx-3"
>
{t("settings.privacy")}
</Link>
</div>
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 pt-4 pb-3 rounded-b-[16px] bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-10">
<Footer />
<div className="absolute bottom-0 left-0 right-0 pt-4 pb-3 rounded-b-[16px] bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-10">
<Footer />
</div>
</div>
</div>
</div>
</div>
<FineTuningAlert />
</>
);
}

Expand Down
28 changes: 28 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -766,3 +766,31 @@ does not extend the close button beyond the viewport. */
display: none;
}
}

.top-banner {
animation: popTop 500ms forwards;
}

@keyframes popTop {
0% {
top: -3.5rem;
}

100% {
top: 0px;
}
}

.rm-top-banner {
animation: rmPopTop 500ms forwards;
}

@keyframes rmPopTop {
0% {
top: 0px;
}

100% {
top: -3.5rem;
}
}
129 changes: 129 additions & 0 deletions frontend/src/models/experimental/fineTuning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders, safeJsonParse } from "@/utils/request";

const FineTuning = {
cacheKeys: {
dismissed_cta: "anythingllm_dismissed_fine_tune_notif",
eligibility: "anythingllm_can_fine_tune",
},

/**
* Get the information for the Fine-tuning product to display in various frontends
* @returns {Promise<{
* productDetails: {
* name: string,
* description: string,
* icon: string,
* active: boolean,
* },
* pricing: {
* usd: number,
* },
* availableBaseModels: string[]
* }>}
*/
info: async function () {
return await fetch(`${API_BASE}/experimental/fine-tuning/info`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error("Could not get model info.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return null;
});
},
datasetStat: async function ({ slugs = [], feedback = null }) {
return await fetch(`${API_BASE}/experimental/fine-tuning/dataset`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ slugs, feedback }),
})
.then((res) => {
if (!res.ok) throw new Error("Could not get dataset info.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return { count: null };
});
},
/**
* Generates Fine-Tuning order.
* @param {{email:string, baseModel:string, modelName: string, trainingData: {slugs:string[], feedback:boolean|null}}} param0
* @returns {Promise<{checkoutUrl:string, jobId:string}|null>}
*/
createOrder: async function ({ email, baseModel, modelName, trainingData }) {
return await fetch(`${API_BASE}/experimental/fine-tuning/order`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({
email,
baseModel,
modelName,
trainingData,
}),
})
.then((res) => {
if (!res.ok) throw new Error("Could not order fine-tune.");
return res.json();
})
.then((res) => res)
.catch((e) => {
console.error(e);
return null;
});
},

/**
* Determine if a user should see the CTA alert. In general this alert
* Can only render if the user is empty (single user) or is an admin role.
* @returns {boolean}
*/
canAlert: function (user = null) {
if (!!user && user.role !== "admin") return false;
return !window?.localStorage?.getItem(this.cacheKeys.dismissed_cta);
},
checkEligibility: async function () {
const cache = window.localStorage.getItem(this.cacheKeys.eligibility);
if (!!cache) {
const { data, lastFetched } = safeJsonParse(cache, {
data: null,
lastFetched: 0,
});
if (!!data && Date.now() - lastFetched < 1.8e7)
// 5 hours
return data.eligible;
}

return await fetch(`${API_BASE}/experimental/fine-tuning/check-eligible`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error("Could not check if eligible.");
return res.json();
})
.then((res) => {
window.localStorage.setItem(
this.cacheKeys.eligibility,
JSON.stringify({
data: { eligible: res.eligible },
lastFetched: Date.now(),
})
);
return res.eligible;
})
.catch((e) => {
console.error(e);
return false;
});
},
};

export default FineTuning;
66 changes: 66 additions & 0 deletions frontend/src/pages/FineTuning/Banner/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useEffect, useState } from "react";
import useUser from "@/hooks/useUser";
import FineTuning from "@/models/experimental/fineTuning";
import { createPortal } from "react-dom";
import { Sparkle } from "@phosphor-icons/react";
import { Link, useLocation } from "react-router-dom";
import paths from "@/utils/paths";

export function FineTuningAlert() {
const { user } = useUser();
const location = useLocation();
const [className, setClassName] = useState("top-banner");
const [isEligible, setIsEligible] = useState(false);

function dismissAlert() {
setClassName("rm-top-banner");
window?.localStorage?.setItem(FineTuning.cacheKeys.dismissed_cta, "1");
setTimeout(() => {
setIsEligible(false);
}, 550);
}

useEffect(() => {
if (!FineTuning.canAlert(user)) return;
if (
location.pathname === paths.orderFineTune() ||
location.pathname === paths.settings.chats()
)
return;
FineTuning.checkEligibility()
.then((eligible) => setIsEligible(eligible))
.catch(() => null);
}, [user]);

if (!isEligible) return null;
return createPortal(
<div
className={`fixed ${className} left-0 right-0 h-14 bg-orange-400 flex items-center justify-end px-4 z-[9999]`}
>
<Link
onClick={dismissAlert}
to={paths.orderFineTune()}
className="grow w-full h-full ml-4 py-1"
>
<div className="flex flex-col items-center w-full">
<div className="flex w-full justify-center items-center gap-x-2">
<Sparkle size={20} className="text-white" />
<p className="text-white font-medium text-lg">
You have enough data for a fine-tune!
</p>
</div>
<p className="text-xs text-white">click to learn more &rarr;</p>
</div>
</Link>
<div className="flex items-center gap-x-2 shrink-0">
<button
onClick={dismissAlert}
className="border-none text-white font-medium text-sm px-[10px] py-[6px] rounded-md bg-white/5 hover:bg-white/10"
>
Dismiss
</button>
</div>
</div>,
document.getElementById("root")
);
}
Loading

0 comments on commit f8e54b2

Please sign in to comment.