这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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