mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-05-02 09:03:12 +00:00
New chat home page (#3555)
* wip getting started checklist page * lint * add new ui skeleton for new home page/checklist * make legacy home page appearance setting * dynamic checklist rendering/close checklist * make home page functional + update cta buttons in ws settings * lint * remove unneeded routes * wip fixing checklist items (create ws and embed doc broken) + news section updates * lint * FINALLY fix all functionality & remove hook to simplify logic * lint * hide/show options based on user role/mum enabled * add hover states and redo checklist ui * remove welcome checklist page * add validation to task completion * polish behavior of checklist/fix roles on checklist items * lint * light mode/use tailwind color classes * remove tutorials link * Modify how legacy page works * small UI updates * remove unused paths cleanup explore features * revert save button changes * conditionally render legacy default chat page when in multiuser * remove role checks in checklist * remove role checks in quick links * remove unused hook * dark mode new home page checklist ui updates * Add news logging to repo for record keeping update new module to pull from CDN * simplify landing markup * light mode styles * remove border in light mode from merge conflict * Update ignores * slide up dismiss * prevent checklist popin * confetti? * fix url hash on navigate * watch for event changes for updating checklist * useMemo and callback memory optimization * move handlers to constants via params fwd * dev * update github text --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
be7b1255da
commit
e76dc0f1ca
25 changed files with 1400 additions and 17 deletions
.github/workflows
extras/support/announcements
frontend
package.json
src
tailwind.config.jsyarn.lock
1
.github/workflows/build-and-push-image.yaml
vendored
1
.github/workflows/build-and-push-image.yaml
vendored
|
@ -25,6 +25,7 @@ on:
|
|||
- 'embed/**/*' # Embed is submodule
|
||||
- 'browser-extension/**/*' # Chrome extension is submodule
|
||||
- 'server/utils/agents/aibitat/example/**/*' # Do not push new image for local dev testing of new aibitat images.
|
||||
- 'extras/**/*' # Extra is just for news and other local content.
|
||||
|
||||
jobs:
|
||||
push_multi_platform_to_registries:
|
||||
|
|
3
.github/workflows/dev-build.yaml
vendored
3
.github/workflows/dev-build.yaml
vendored
|
@ -6,7 +6,7 @@ concurrency:
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: ['3000-mcp-compatibility'] # put your current branch to create a build. Core team only.
|
||||
branches: ['3536-feat-new-chat-home-page'] # put your current branch to create a build. Core team only.
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'cloud-deployments/*'
|
||||
|
@ -18,6 +18,7 @@ on:
|
|||
- 'embed/**/*' # Embed should be published to frontend (yarn build:publish) if any changes are introduced
|
||||
- 'browser-extension/**/*' # Chrome extension is submodule
|
||||
- 'server/utils/agents/aibitat/example/**/*' # Do not push new image for local dev testing of new aibitat images.
|
||||
- 'extras/**/*' # Extra is just for news and other local content.
|
||||
|
||||
jobs:
|
||||
push_multi_platform_to_registries:
|
||||
|
|
26
extras/support/announcements/2025-04-08.json
Normal file
26
extras/support/announcements/2025-04-08.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
{
|
||||
"thumbnail_url": "https://cdn.anythingllm.com/support/announcements/assets/mcp.jpg",
|
||||
"title": "MCP Support",
|
||||
"short_description": "Import and leverage MCP tools using AnythingLLM.",
|
||||
"goto": "https://docs.anythingllm.com/mcp-compatibility/overview",
|
||||
"author": "AnythingLLM",
|
||||
"date": "April 8, 2025"
|
||||
},
|
||||
{
|
||||
"thumbnail_url": "https://blogs.nvidia.com/wp-content/uploads/2025/03/nv-raig-032525-nv-blog-1280x680-1-scaled.jpg",
|
||||
"title": "NVIDIA NIM Support",
|
||||
"short_description": "Unlock the power of NVIDIA NIM on Windows with RTX GPU in our latest update via the NVIDIA NIM LLM provider.",
|
||||
"goto": "https://blogs.nvidia.com/blog/rtx-ai-garage-nim-blueprints-g-assist",
|
||||
"author": "NVIDIA",
|
||||
"date": "March 25, 2025"
|
||||
},
|
||||
{
|
||||
"thumbnail_url": null,
|
||||
"title": "Community Hub Updates",
|
||||
"short_description": "We refreshed the Community Hub with a new look and feel. Check it out!",
|
||||
"goto": "https://hub.anythingllm.com",
|
||||
"author": "AnythingLLM",
|
||||
"date": "March 12, 2025"
|
||||
}
|
||||
]
|
BIN
extras/support/announcements/assets/mcp.jpg
Normal file
BIN
extras/support/announcements/assets/mcp.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 18 KiB |
1
extras/support/announcements/list.txt
Normal file
1
extras/support/announcements/list.txt
Normal file
|
@ -0,0 +1 @@
|
|||
2025-04-08.json
|
|
@ -30,6 +30,7 @@
|
|||
"pluralize": "^8.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "13.1.1",
|
||||
"react-confetti-explosion": "^2.1.2",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
|
|
|
@ -49,6 +49,16 @@ export function AvailableAgents({
|
|||
}) {
|
||||
const formRef = useRef(null);
|
||||
const agentSessionActive = useIsAgentSessionActive();
|
||||
|
||||
/*
|
||||
* @checklist-item
|
||||
* If the URL has the #agent hash, open the agent menu for the user
|
||||
* automatically when the component mounts.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (window.location.hash === "#agent" && !showing) handleAgentClick();
|
||||
}, [promptRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
function listenForOutsideClick() {
|
||||
if (!showing || !formRef.current) return false;
|
||||
|
@ -64,6 +74,12 @@ export function AvailableAgents({
|
|||
setShowing(false);
|
||||
};
|
||||
|
||||
const handleAgentClick = () => {
|
||||
setShowing(false);
|
||||
sendCommand("@agent ", false);
|
||||
promptRef?.current?.focus();
|
||||
};
|
||||
|
||||
if (agentSessionActive) return null;
|
||||
return (
|
||||
<>
|
||||
|
@ -74,11 +90,7 @@ export function AvailableAgents({
|
|||
className="w-[600px] p-2 bg-theme-action-menu-bg rounded-2xl shadow flex-col justify-center items-start gap-2.5 inline-flex"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowing(false);
|
||||
sendCommand("@agent ", false);
|
||||
promptRef?.current?.focus();
|
||||
}}
|
||||
onClick={handleAgentClick}
|
||||
className="border-none w-full hover:cursor-pointer hover:bg-theme-action-menu-item-hover px-2 py-2 rounded-xl flex flex-col justify-start group"
|
||||
>
|
||||
<div className="w-full flex-col text-left flex pointer-events-none">
|
||||
|
|
|
@ -26,6 +26,17 @@ export default function SlashPresets({ setShowing, sendCommand, promptRef }) {
|
|||
useEffect(() => {
|
||||
fetchPresets();
|
||||
}, []);
|
||||
|
||||
/*
|
||||
* @checklist-item
|
||||
* If the URL has the #slash-commands hash, open the add modal for the user
|
||||
* automatically when the component mounts.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (window.location.hash === "#slash-commands" && !isAddModalOpen)
|
||||
openAddModal();
|
||||
}, []);
|
||||
|
||||
if (isActiveAgentSession) return null;
|
||||
|
||||
const fetchPresets = async () => {
|
||||
|
|
|
@ -48,6 +48,34 @@
|
|||
--theme-file-row-selected-even: rgba(14, 165, 233, 0.2);
|
||||
--theme-file-row-selected-odd: rgba(14, 165, 233, 0.1);
|
||||
--theme-file-picker-hover: rgb(14 165 233 / 0.2);
|
||||
|
||||
--theme-home-text: #ffffff;
|
||||
--theme-home-text-secondary: #9f9fa0;
|
||||
--theme-home-bg-card: #1a1b1b;
|
||||
--theme-home-bg-button: #252626;
|
||||
--theme-home-border: rgba(255, 255, 255, 0.2);
|
||||
--theme-home-button-primary: #36bffa;
|
||||
--theme-home-button-primary-hover: rgba(54, 191, 250, 0.9);
|
||||
--theme-home-button-secondary: #27282a;
|
||||
--theme-home-button-secondary-hover: rgba(54, 191, 250, 0.1);
|
||||
--theme-home-button-secondary-text: #ffffff;
|
||||
--theme-home-button-secondary-hover-text: #36bffa;
|
||||
--theme-home-update-card-bg: #1c1c1c;
|
||||
--theme-home-update-card-hover: #252525;
|
||||
--theme-home-update-source: #53b1fd;
|
||||
|
||||
--theme-checklist-item-bg: #203c48;
|
||||
--theme-checklist-item-text: #b9e6fe;
|
||||
--theme-checklist-item-completed-bg: #36463d;
|
||||
--theme-checklist-item-completed-text: #a6f4c5;
|
||||
--theme-checklist-checkbox-fill: #a6f4c5;
|
||||
--theme-checklist-checkbox-text: #36463d;
|
||||
--theme-checklist-item-hover: #36bffa;
|
||||
--theme-checklist-checkbox-border: #ffffff;
|
||||
--theme-checklist-button-border: #36bffa;
|
||||
--theme-checklist-button-text: #36bffa;
|
||||
--theme-checklist-button-hover-bg: rgba(54, 191, 250, 0.2);
|
||||
--theme-checklist-button-hover-border: rgba(54, 191, 250, 0.8);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
|
@ -101,6 +129,34 @@
|
|||
--theme-file-row-selected-even: #0ba5ec;
|
||||
--theme-file-row-selected-odd: #0ba5ec;
|
||||
--theme-file-picker-hover: #e2e7ee;
|
||||
|
||||
--theme-home-text: #0e0f0f;
|
||||
--theme-home-text-secondary: #3f3f42;
|
||||
--theme-home-bg-card: #edf2fa;
|
||||
--theme-home-bg-button: #f3f4f6;
|
||||
--theme-home-border: rgba(0, 0, 0, 0.1);
|
||||
--theme-home-button-primary: #36bffa;
|
||||
--theme-home-button-primary-hover: rgba(54, 191, 250, 0.9);
|
||||
--theme-home-button-secondary: #dbe8fe;
|
||||
--theme-home-button-secondary-hover: #e5e7eb;
|
||||
--theme-home-button-secondary-text: #293056;
|
||||
--theme-home-button-secondary-hover-text: #0ba5ec;
|
||||
--theme-home-update-card-bg: #edf2fa;
|
||||
--theme-home-update-card-hover: #f3f4f6;
|
||||
--theme-home-update-source: #0284c7;
|
||||
|
||||
--theme-checklist-item-bg: #c8e8fa;
|
||||
--theme-checklist-item-text: #0d3851;
|
||||
--theme-checklist-item-completed-bg: #d8f3ea;
|
||||
--theme-checklist-item-completed-text: #039855;
|
||||
--theme-checklist-checkbox-fill: #6ce9a6;
|
||||
--theme-checklist-checkbox-text: #ffffff;
|
||||
--theme-checklist-item-hover: #0ba5ec;
|
||||
--theme-checklist-checkbox-border: #6b7280;
|
||||
--theme-checklist-button-border: #0ba5ec;
|
||||
--theme-checklist-button-text: #0ba5ec;
|
||||
--theme-checklist-button-hover-bg: rgba(11, 165, 236, 0.1);
|
||||
--theme-checklist-button-hover-border: rgba(11, 165, 236, 0.8);
|
||||
}
|
||||
|
||||
[data-theme="light"] .text-white {
|
||||
|
@ -991,3 +1047,8 @@ does not extend the close button beyond the viewport. */
|
|||
.animate-thoughtTransition {
|
||||
animation: thoughtTransition 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.checklist-completed {
|
||||
-webkit-animation: fadein 0.3s linear forwards;
|
||||
animation: fadein 0.3s linear forwards;
|
||||
}
|
||||
|
|
BIN
frontend/src/media/announcements/placeholder-1.png
Normal file
BIN
frontend/src/media/announcements/placeholder-1.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 21 KiB |
BIN
frontend/src/media/announcements/placeholder-2.png
Normal file
BIN
frontend/src/media/announcements/placeholder-2.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 22 KiB |
BIN
frontend/src/media/announcements/placeholder-3.png
Normal file
BIN
frontend/src/media/announcements/placeholder-3.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 22 KiB |
|
@ -0,0 +1,28 @@
|
|||
import React from "react";
|
||||
|
||||
export default function SlashCommandIcon({ className }) {
|
||||
return (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="1"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="3.5"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.18103 10.7974L9.8189 4.20508"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import { useState } from "react";
|
||||
import { CHECKLIST_STORAGE_KEY, CHECKLIST_UPDATED_EVENT } from "../constants";
|
||||
import { Check } from "@phosphor-icons/react";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export function ChecklistItem({ id, title, action, onAction, icon: Icon }) {
|
||||
const [isCompleted, setIsCompleted] = useState(() => {
|
||||
const stored = window.localStorage.getItem(CHECKLIST_STORAGE_KEY);
|
||||
if (!stored) return false;
|
||||
const completedItems = safeJsonParse(stored, {});
|
||||
return completedItems[id] || false;
|
||||
});
|
||||
|
||||
const handleClick = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!isCompleted) {
|
||||
const shouldComplete = await onAction();
|
||||
if (shouldComplete) {
|
||||
const stored = window.localStorage.getItem(CHECKLIST_STORAGE_KEY);
|
||||
const completedItems = stored ? JSON.parse(stored) : {};
|
||||
completedItems[id] = true;
|
||||
window.localStorage.setItem(
|
||||
CHECKLIST_STORAGE_KEY,
|
||||
JSON.stringify(completedItems)
|
||||
);
|
||||
setIsCompleted(true);
|
||||
window.dispatchEvent(new CustomEvent(CHECKLIST_UPDATED_EVENT));
|
||||
}
|
||||
} else {
|
||||
await onAction();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-x-4 transition-colors cursor-pointer rounded-lg p-3 group ${
|
||||
isCompleted
|
||||
? "bg-theme-checklist-item-completed-bg"
|
||||
: "bg-theme-checklist-item-bg"
|
||||
}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{Icon && (
|
||||
<div className="flex-shrink-0">
|
||||
<Icon
|
||||
size={18}
|
||||
className={
|
||||
isCompleted
|
||||
? "text-theme-checklist-item-completed-text"
|
||||
: "text-theme-checklist-item-text"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<h3
|
||||
className={`text-sm font-medium transition-colors duration-200 ${
|
||||
isCompleted
|
||||
? "text-theme-checklist-item-completed-text line-through"
|
||||
: "text-theme-checklist-item-text group-hover:text-theme-checklist-item-hover light:group-hover:text-theme-checklist-item-text-secondary"
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
{isCompleted ? (
|
||||
<div className="w-5 h-5 rounded-full bg-theme-checklist-checkbox-fill flex items-center justify-center">
|
||||
<Check
|
||||
size={14}
|
||||
weight="bold"
|
||||
className="text-theme-checklist-checkbox-text"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button className="w-[64px] h-[24px] rounded-md bg-white/10 light:bg-white/70 text-theme-checklist-item-text font-semibold text-xs transition-all duration-200 flex items-center justify-center hover:bg-white/20 light:hover:bg-white/60">
|
||||
{action}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
162
frontend/src/pages/Main/Home/Checklist/constants.js
Normal file
162
frontend/src/pages/Main/Home/Checklist/constants.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
import {
|
||||
SquaresFour,
|
||||
ChatDots,
|
||||
Files,
|
||||
ChatCenteredText,
|
||||
UsersThree,
|
||||
} from "@phosphor-icons/react";
|
||||
import SlashCommandIcon from "./ChecklistItem/icons/SlashCommand";
|
||||
import paths from "@/utils/paths";
|
||||
const noop = () => {};
|
||||
|
||||
export const CHECKLIST_UPDATED_EVENT = "anythingllm_checklist_updated";
|
||||
export const CHECKLIST_STORAGE_KEY = "anythingllm_checklist_completed";
|
||||
export const CHECKLIST_HIDDEN = "anythingllm_checklist_dismissed";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChecklistItemHandlerParams
|
||||
* @property {Object[]} workspaces - Array of workspaces
|
||||
* @property {Function} navigate - Function to navigate to a path
|
||||
* @property {Function} setSelectedWorkspace - Function to set the selected workspace
|
||||
* @property {Function} showManageWsModal - Function to show the manage workspace modal
|
||||
* @property {Function} showToast - Function to show a toast
|
||||
* @property {Function} showNewWsModal - Function to show the new workspace modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChecklistItem
|
||||
* @property {string} id
|
||||
* @property {string} title
|
||||
* @property {string} description
|
||||
* @property {string} action
|
||||
* @property {(params: ChecklistItemHandlerParams) => boolean} handler
|
||||
* @property {string} icon
|
||||
* @property {boolean} completed
|
||||
*/
|
||||
|
||||
/** @type {ChecklistItem[]} */
|
||||
export const CHECKLIST_ITEMS = [
|
||||
{
|
||||
id: "create_workspace",
|
||||
title: "Create a workspace",
|
||||
description: "Create your first workspace to get started",
|
||||
action: "Create",
|
||||
handler: ({ showNewWsModal = noop }) => {
|
||||
showNewWsModal();
|
||||
return true;
|
||||
},
|
||||
icon: SquaresFour,
|
||||
},
|
||||
{
|
||||
id: "send_chat",
|
||||
title: "Send a chat",
|
||||
description: "Start a conversation with your AI assistant",
|
||||
action: "Chat",
|
||||
handler: ({
|
||||
workspaces = [],
|
||||
navigate = noop,
|
||||
showToast = noop,
|
||||
showNewWsModal = noop,
|
||||
}) => {
|
||||
if (workspaces.length === 0) {
|
||||
showToast(
|
||||
"Please create a workspace before starting a chat.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
return false;
|
||||
}
|
||||
navigate(paths.workspace.chat(workspaces[0].slug));
|
||||
return true;
|
||||
},
|
||||
icon: ChatDots,
|
||||
},
|
||||
{
|
||||
id: "embed_document",
|
||||
title: "Embed a document",
|
||||
description: "Add your first document to your workspace",
|
||||
action: "Embed",
|
||||
handler: ({
|
||||
workspaces = [],
|
||||
setSelectedWorkspace = noop,
|
||||
showManageWsModal = noop,
|
||||
showToast = noop,
|
||||
showNewWsModal = noop,
|
||||
}) => {
|
||||
if (workspaces.length === 0) {
|
||||
showToast(
|
||||
"Please create a workspace before embedding documents.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
return false;
|
||||
}
|
||||
setSelectedWorkspace(workspaces[0]);
|
||||
showManageWsModal();
|
||||
return true;
|
||||
},
|
||||
icon: Files,
|
||||
},
|
||||
{
|
||||
id: "setup_system_prompt",
|
||||
title: "Set up a system prompt",
|
||||
description: "Configure your AI assistant's behavior",
|
||||
action: "Set Up",
|
||||
handler: ({
|
||||
workspaces = [],
|
||||
navigate = noop,
|
||||
showNewWsModal = noop,
|
||||
showToast = noop,
|
||||
}) => {
|
||||
if (workspaces.length === 0) {
|
||||
showToast(
|
||||
"Please create a workspace before setting up system prompts.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
return false;
|
||||
}
|
||||
navigate(paths.workspace.settings.chatSettings(workspaces[0].slug));
|
||||
window.location.hash = "#system-prompts";
|
||||
return true;
|
||||
},
|
||||
icon: ChatCenteredText,
|
||||
},
|
||||
{
|
||||
id: "define_slash_command",
|
||||
title: "Define a slash command",
|
||||
description: "Create custom commands for your assistant",
|
||||
action: "Define",
|
||||
handler: ({
|
||||
workspaces = [],
|
||||
navigate = noop,
|
||||
showNewWsModal = noop,
|
||||
showToast = noop,
|
||||
}) => {
|
||||
if (workspaces.length === 0) {
|
||||
showToast(
|
||||
"Please create a workspace before setting up slash commands.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
return false;
|
||||
}
|
||||
navigate(paths.workspace.chat(workspaces[0].slug));
|
||||
window.location.hash = "#slash-commands";
|
||||
return true;
|
||||
},
|
||||
icon: SlashCommandIcon,
|
||||
},
|
||||
{
|
||||
id: "visit_community",
|
||||
title: "Visit Community Hub",
|
||||
description: "Explore community resources and templates",
|
||||
action: "Browse",
|
||||
handler: () => window.open(paths.communityHub.website(), "_blank"),
|
||||
icon: UsersThree,
|
||||
},
|
||||
];
|
213
frontend/src/pages/Main/Home/Checklist/index.jsx
Normal file
213
frontend/src/pages/Main/Home/Checklist/index.jsx
Normal file
|
@ -0,0 +1,213 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import ManageWorkspace, {
|
||||
useManageWorkspaceModal,
|
||||
} from "@/components/Modals/ManageWorkspace";
|
||||
import NewWorkspaceModal, {
|
||||
useNewWorkspaceModal,
|
||||
} from "@/components/Modals/NewWorkspace";
|
||||
import Workspace from "@/models/workspace";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChecklistItem } from "./ChecklistItem";
|
||||
import showToast from "@/utils/toast";
|
||||
import {
|
||||
CHECKLIST_HIDDEN,
|
||||
CHECKLIST_STORAGE_KEY,
|
||||
CHECKLIST_ITEMS,
|
||||
CHECKLIST_UPDATED_EVENT,
|
||||
} from "./constants";
|
||||
import ConfettiExplosion from "react-confetti-explosion";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
const MemoizedChecklistItem = React.memo(ChecklistItem);
|
||||
export default function Checklist() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
const [completedCount, setCompletedCount] = useState(0);
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [selectedWorkspace, setSelectedWorkspace] = useState(null);
|
||||
const [workspaces, setWorkspaces] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
const containerRef = useRef(null);
|
||||
const {
|
||||
showModal: showNewWsModal,
|
||||
hideModal: hideNewWsModal,
|
||||
showing: showingNewWsModal,
|
||||
} = useNewWorkspaceModal();
|
||||
const { showModal: showManageWsModal, hideModal: hideManageWsModal } =
|
||||
useManageWorkspaceModal();
|
||||
|
||||
const createItemHandler = useCallback(
|
||||
(item) => {
|
||||
return () =>
|
||||
item.handler({
|
||||
workspaces,
|
||||
navigate,
|
||||
setSelectedWorkspace,
|
||||
showManageWsModal,
|
||||
showToast,
|
||||
showNewWsModal,
|
||||
});
|
||||
},
|
||||
[
|
||||
workspaces,
|
||||
navigate,
|
||||
setSelectedWorkspace,
|
||||
showManageWsModal,
|
||||
showToast,
|
||||
showNewWsModal,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function initialize() {
|
||||
try {
|
||||
const hidden = window.localStorage.getItem(CHECKLIST_HIDDEN);
|
||||
setIsHidden(!!hidden);
|
||||
// If the checklist is hidden, don't bother evaluating it.
|
||||
if (hidden) return;
|
||||
|
||||
// If the checklist is completed then dont continue and just show the completed state.
|
||||
const checklist = window.localStorage.getItem(CHECKLIST_STORAGE_KEY);
|
||||
const existingChecklist = checklist ? safeJsonParse(checklist, {}) : {};
|
||||
const isCompleted =
|
||||
Object.keys(existingChecklist).length === CHECKLIST_ITEMS.length;
|
||||
setIsCompleted(isCompleted);
|
||||
if (isCompleted) return;
|
||||
|
||||
// Otherwise, we can fetch workspaces for our checklist tasks as well
|
||||
// as determine if the create_workspace task is completed for pre-checking.
|
||||
const workspaces = await Workspace.all();
|
||||
setWorkspaces(workspaces);
|
||||
if (workspaces.length > 0) {
|
||||
existingChecklist["create_workspace"] = true;
|
||||
window.localStorage.setItem(
|
||||
CHECKLIST_STORAGE_KEY,
|
||||
JSON.stringify(existingChecklist)
|
||||
);
|
||||
}
|
||||
|
||||
evaluateChecklist(); // Evaluate checklist on mount.
|
||||
window.addEventListener(CHECKLIST_UPDATED_EVENT, evaluateChecklist);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
return () => {
|
||||
window.removeEventListener(CHECKLIST_UPDATED_EVENT, evaluateChecklist);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchWorkspaces = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
setWorkspaces(workspaces);
|
||||
};
|
||||
fetchWorkspaces();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCompleted) {
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
}, 5_000);
|
||||
}
|
||||
}, [isCompleted]);
|
||||
|
||||
const evaluateChecklist = useCallback(() => {
|
||||
try {
|
||||
const checklist = window.localStorage.getItem(CHECKLIST_STORAGE_KEY);
|
||||
if (!checklist) return;
|
||||
const completedItems = safeJsonParse(checklist, {});
|
||||
setCompletedCount(Object.keys(completedItems).length);
|
||||
setIsCompleted(
|
||||
Object.keys(completedItems).length === CHECKLIST_ITEMS.length
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
window.localStorage.setItem(CHECKLIST_HIDDEN, "true");
|
||||
if (containerRef?.current) containerRef.current.style.height = "0px";
|
||||
}, []);
|
||||
if (isHidden || loading) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="transition-height duration-300 h-[100%] overflow-y-hidden relative"
|
||||
>
|
||||
<div
|
||||
className={`${isCompleted ? "checklist-completed" : "hidden"} absolute top-0 left-0 w-full h-full p-2 z-10 transition-all duration-300`}
|
||||
>
|
||||
{isCompleted && (
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<ConfettiExplosion force={0.25} duration={3000} />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{}}
|
||||
className="bg-[rgba(54,70,61,0.5)] light:bg-[rgba(216,243,234,0.5)] w-full h-full flex items-center justify-center bg-theme-checklist-item-completed-bg/50 rounded-lg"
|
||||
>
|
||||
<p className="text-theme-checklist-item-completed-text text-lg font-bold">
|
||||
You're on your way to becoming an AnythingLLM expert!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-lg p-4 lg:p-6 bg-theme-home-bg-card relative ${isCompleted ? "blur-sm" : ""}`}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<h1 className="text-theme-home-text uppercase text-sm font-semibold">
|
||||
Getting Started
|
||||
</h1>
|
||||
{CHECKLIST_ITEMS.length - completedCount > 0 && (
|
||||
<p className="text-theme-home-text-secondary text-xs">
|
||||
{CHECKLIST_ITEMS.length - completedCount} tasks left
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="text-theme-home-text-secondary bg-theme-home-bg-button px-3 py-1 rounded-xl hover:bg-white/10 transition-colors text-xs light:bg-black-100"
|
||||
>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{CHECKLIST_ITEMS.map((item) => (
|
||||
<MemoizedChecklistItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
action={item.action}
|
||||
icon={item.icon}
|
||||
completed={item.completed}
|
||||
onAction={createItemHandler(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
|
||||
{selectedWorkspace && (
|
||||
<ManageWorkspace
|
||||
providedSlug={selectedWorkspace.slug}
|
||||
hideModal={() => {
|
||||
setSelectedWorkspace(null);
|
||||
hideManageWsModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
124
frontend/src/pages/Main/Home/ExploreFeatures/index.jsx
Normal file
124
frontend/src/pages/Main/Home/ExploreFeatures/index.jsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import Workspace from "@/models/workspace";
|
||||
|
||||
export default function ExploreFeatures() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const chatWithAgent = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length > 0) {
|
||||
const firstWorkspace = workspaces[0];
|
||||
navigate(paths.workspace.chat(firstWorkspace.slug));
|
||||
window.location.hash = "#agent";
|
||||
}
|
||||
};
|
||||
|
||||
const buildAgentFlow = () => navigate(paths.agents.builder());
|
||||
const setSlashCommand = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length > 0) {
|
||||
const firstWorkspace = workspaces[0];
|
||||
navigate(paths.workspace.chat(firstWorkspace.slug));
|
||||
window.location.hash = "#slash-commands";
|
||||
}
|
||||
};
|
||||
|
||||
const exploreSlashCommands = () => {
|
||||
window.open(paths.communityHub.viewMoreOfType("slash-commands"), "_blank");
|
||||
};
|
||||
|
||||
const setSystemPrompt = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length > 0) {
|
||||
const firstWorkspace = workspaces[0];
|
||||
navigate(paths.workspace.settings.chatSettings(firstWorkspace.slug));
|
||||
window.location.hash = "#system-prompts";
|
||||
}
|
||||
};
|
||||
|
||||
const managePromptVariables = () => {
|
||||
navigate(paths.settings.systemPromptVariables());
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-theme-home-text uppercase text-sm font-semibold mb-4">
|
||||
Explore more features
|
||||
</h1>
|
||||
<div className="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<FeatureCard
|
||||
title="Custom AI Agents"
|
||||
description="Enable powerful automation and workflow extensions for your specific needs with no code."
|
||||
primaryAction="Chat using @agent"
|
||||
secondaryAction="Build an agent flow"
|
||||
onPrimaryAction={chatWithAgent}
|
||||
onSecondaryAction={buildAgentFlow}
|
||||
isNew={true}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Slash Commands"
|
||||
description="Save time and inject prompts using custom slash commands."
|
||||
primaryAction="Create a Slash Command"
|
||||
secondaryAction="Explore on Hub"
|
||||
onPrimaryAction={setSlashCommand}
|
||||
onSecondaryAction={exploreSlashCommands}
|
||||
isNew={false}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="System Prompts"
|
||||
description="Modify the system prompt to customize the AI replies of a workspace."
|
||||
primaryAction="Modify a System Prompt"
|
||||
secondaryAction="Manage prompt variables"
|
||||
onPrimaryAction={setSystemPrompt}
|
||||
onSecondaryAction={managePromptVariables}
|
||||
isNew={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FeatureCard({
|
||||
title,
|
||||
description,
|
||||
primaryAction,
|
||||
secondaryAction,
|
||||
onPrimaryAction,
|
||||
onSecondaryAction,
|
||||
isNew,
|
||||
}) {
|
||||
return (
|
||||
<div className="border border-theme-home-border rounded-lg py-4 px-5 flex flex-col justify-between gap-y-4">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h2 className="text-theme-home-text font-semibold flex items-center gap-x-2">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-theme-home-text-secondary text-sm">{description}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-[10px]">
|
||||
<button
|
||||
onClick={onPrimaryAction}
|
||||
className="w-full h-[36px] border border-white/20 light:border-black/20 text-white rounded-lg text-theme-home-button-primary-text text-sm font-medium flex items-center justify-center gap-x-2.5 transition-all duration-200 hover:bg-theme-home-button-secondary-hover hover:text-theme-home-button-secondary-hover-text"
|
||||
>
|
||||
{primaryAction}
|
||||
</button>
|
||||
{secondaryAction && (
|
||||
<div className="relative w-full">
|
||||
{isNew && (
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 px-2 font-semibold rounded-md text-[10px] text-theme-checklist-item-text bg-theme-checklist-item-bg light:bg-white/60">
|
||||
New
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={onSecondaryAction}
|
||||
className="w-full h-[36px] bg-theme-home-button-secondary rounded-lg text-theme-home-button-secondary-text text-sm font-medium flex items-center justify-center transition-all duration-200 hover:bg-theme-home-button-secondary-hover hover:text-theme-home-button-secondary-hover-text"
|
||||
>
|
||||
{secondaryAction}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
98
frontend/src/pages/Main/Home/QuickLinks/index.jsx
Normal file
98
frontend/src/pages/Main/Home/QuickLinks/index.jsx
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { ChatCenteredDots, FileArrowDown, Plus } from "@phosphor-icons/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Workspace from "@/models/workspace";
|
||||
import paths from "@/utils/paths";
|
||||
import { useManageWorkspaceModal } from "@/components/Modals/ManageWorkspace";
|
||||
import ManageWorkspace from "@/components/Modals/ManageWorkspace";
|
||||
import { useState } from "react";
|
||||
import { useNewWorkspaceModal } from "@/components/Modals/NewWorkspace";
|
||||
import NewWorkspaceModal from "@/components/Modals/NewWorkspace";
|
||||
import showToast from "@/utils/toast";
|
||||
|
||||
export default function QuickLinks() {
|
||||
const navigate = useNavigate();
|
||||
const { showModal } = useManageWorkspaceModal();
|
||||
const [selectedWorkspace, setSelectedWorkspace] = useState(null);
|
||||
const {
|
||||
showing: showingNewWsModal,
|
||||
showModal: showNewWsModal,
|
||||
hideModal: hideNewWsModal,
|
||||
} = useNewWorkspaceModal();
|
||||
|
||||
const sendChat = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length > 0) {
|
||||
const firstWorkspace = workspaces[0];
|
||||
navigate(paths.workspace.chat(firstWorkspace.slug));
|
||||
} else {
|
||||
showToast(
|
||||
"Please create a workspace before starting a chat.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const embedDocument = async () => {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length > 0) {
|
||||
const firstWorkspace = workspaces[0];
|
||||
setSelectedWorkspace(firstWorkspace);
|
||||
showModal();
|
||||
} else {
|
||||
showToast(
|
||||
"Please create a workspace before embedding documents.",
|
||||
"warning",
|
||||
{ clear: true }
|
||||
);
|
||||
showNewWsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const createWorkspace = () => {
|
||||
showNewWsModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-theme-home-text uppercase text-sm font-semibold mb-4">
|
||||
Quick Links
|
||||
</h1>
|
||||
<div className="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<button
|
||||
onClick={sendChat}
|
||||
className="h-[45px] bg-theme-home-button-secondary rounded-lg text-theme-home-button-secondary-text flex items-center justify-center gap-x-2.5 transition-all duration-200 hover:bg-theme-home-button-secondary-hover hover:text-theme-home-button-secondary-hover-text"
|
||||
>
|
||||
<ChatCenteredDots size={16} />
|
||||
Send Chat
|
||||
</button>
|
||||
<button
|
||||
onClick={embedDocument}
|
||||
className="h-[45px] bg-theme-home-button-secondary rounded-lg text-theme-home-button-secondary-text flex items-center justify-center gap-x-2.5 transition-all duration-200 hover:bg-theme-home-button-secondary-hover hover:text-theme-home-button-secondary-hover-text"
|
||||
>
|
||||
<FileArrowDown size={16} />
|
||||
Embed a Document
|
||||
</button>
|
||||
<button
|
||||
onClick={createWorkspace}
|
||||
className="h-[45px] bg-theme-home-button-secondary rounded-lg text-theme-home-button-secondary-text flex items-center justify-center gap-x-2.5 transition-all duration-200 hover:bg-theme-home-button-secondary-hover hover:text-theme-home-button-secondary-hover-text"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Create Workspace
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{selectedWorkspace && (
|
||||
<ManageWorkspace
|
||||
providedSlug={selectedWorkspace.slug}
|
||||
hideModal={() => {
|
||||
setSelectedWorkspace(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
|
||||
</div>
|
||||
);
|
||||
}
|
32
frontend/src/pages/Main/Home/Resources/index.jsx
Normal file
32
frontend/src/pages/Main/Home/Resources/index.jsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import paths from "@/utils/paths";
|
||||
import { ArrowCircleUpRight } from "@phosphor-icons/react";
|
||||
|
||||
export default function Resources() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-theme-home-text uppercase text-sm font-semibold mb-4">
|
||||
Resources
|
||||
</h1>
|
||||
<div className="flex gap-x-6">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer "
|
||||
href={paths.docs()}
|
||||
className="text-theme-home-text text-sm flex items-center gap-x-2 hover:opacity-70"
|
||||
>
|
||||
Docs
|
||||
<ArrowCircleUpRight weight="fill" size={16} />
|
||||
</a>
|
||||
<a
|
||||
href={paths.github()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-theme-home-text text-sm flex items-center gap-x-2 hover:opacity-70"
|
||||
>
|
||||
Star on Github
|
||||
<ArrowCircleUpRight weight="fill" size={16} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
225
frontend/src/pages/Main/Home/Updates/index.jsx
Normal file
225
frontend/src/pages/Main/Home/Updates/index.jsx
Normal file
|
@ -0,0 +1,225 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
import { ArrowSquareOut } from "@phosphor-icons/react";
|
||||
import { Link } from "react-router-dom";
|
||||
import PlaceholderOne from "@/media/announcements/placeholder-1.png";
|
||||
import PlaceholderTwo from "@/media/announcements/placeholder-2.png";
|
||||
import PlaceholderThree from "@/media/announcements/placeholder-3.png";
|
||||
|
||||
/**
|
||||
* @typedef {Object} NewsItem
|
||||
* @property {string} title
|
||||
* @property {string|null} thumbnail_url
|
||||
* @property {string} short_description
|
||||
* @property {string|null} goto
|
||||
* @property {string|null} source
|
||||
* @property {string|null} date
|
||||
*/
|
||||
|
||||
const NEWS_CACHE_CONFIG = {
|
||||
articles: "https://cdn.anythingllm.com/support/announcements/list.txt",
|
||||
announcementsDir: "https://cdn.anythingllm.com/support/announcements",
|
||||
cacheKey: "anythingllm_announcements",
|
||||
ttl: 7 * 24 * 60 * 60 * 1000, // 1 week
|
||||
};
|
||||
|
||||
const PLACEHOLDERS = [PlaceholderOne, PlaceholderTwo, PlaceholderThree];
|
||||
|
||||
function randomPlaceholder() {
|
||||
return PLACEHOLDERS[Math.floor(Math.random() * PLACEHOLDERS.length)];
|
||||
}
|
||||
|
||||
export default function Updates() {
|
||||
const { isLoading, news } = useNewsItems();
|
||||
if (isLoading || !news?.length) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-theme-home-text uppercase text-sm font-semibold mb-4">
|
||||
Updates & Announcements
|
||||
</h1>
|
||||
<div className="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{news.map((item, index) => (
|
||||
<AnnouncementCard
|
||||
key={index}
|
||||
thumbnail_url={item.thumbnail_url}
|
||||
title={item.title}
|
||||
subtitle={item.short_description}
|
||||
source={item.source}
|
||||
date={item.date}
|
||||
goto={item.goto}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getSource(goto) {
|
||||
if (!goto) return null;
|
||||
const url = new URL(goto);
|
||||
return url.hostname;
|
||||
}
|
||||
|
||||
function isExternal(goto) {
|
||||
if (!goto) return false;
|
||||
const url = new URL(goto);
|
||||
return url.hostname !== window.location.hostname;
|
||||
}
|
||||
|
||||
function AnnouncementCard({
|
||||
thumbnail_url = null,
|
||||
title = "",
|
||||
subtitle = "",
|
||||
author = "AnythingLLM",
|
||||
date = null,
|
||||
goto = "#",
|
||||
}) {
|
||||
const placeHolderImage = randomPlaceholder();
|
||||
const source = getSource(goto);
|
||||
const isExternalLink = isExternal(goto);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={goto}
|
||||
target={isExternalLink ? "_blank" : "_self"}
|
||||
rel="noopener noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<div className="bg-theme-home-update-card-bg rounded-xl p-4 flex gap-x-4 hover:bg-theme-home-update-card-hover transition-colors">
|
||||
<img
|
||||
src={thumbnail_url ?? placeHolderImage}
|
||||
alt={title}
|
||||
loading="lazy"
|
||||
onError={(e) => (e.target.src = placeHolderImage)}
|
||||
className="w-[80px] h-[80px] rounded-lg flex-shrink-0 object-cover"
|
||||
/>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex items-center gap-x-[1px]">
|
||||
<p className="text-theme-home-text-secondary text-xs">{source}</p>
|
||||
{isExternalLink && (
|
||||
<ArrowSquareOut
|
||||
size={12}
|
||||
weight="bold"
|
||||
className="text-theme-home-text-secondary mb-[1.5px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-theme-home-text font-medium text-sm">{title}</h3>
|
||||
<p className="text-theme-home-text-secondary text-xs line-clamp-2">
|
||||
{subtitle}
|
||||
</p>
|
||||
<div className="flex items-center gap-x-4 text-xs text-theme-home-text-secondary">
|
||||
<span className="text-theme-home-update-source">{author}</span>
|
||||
<span>{date ?? "Recently"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached news from localStorage if it exists and is valid by ttl timestamp
|
||||
* @returns {null|NewsItem[]} - Array of news items
|
||||
*/
|
||||
function getCachedNews() {
|
||||
try {
|
||||
const cachedNews = localStorage.getItem(NEWS_CACHE_CONFIG.cacheKey);
|
||||
if (!cachedNews) return null;
|
||||
|
||||
/** @type {{news: NewsItem[]|null, timestamp: number|null}|null} */
|
||||
const parsedNews = safeJsonParse(cachedNews, null);
|
||||
if (!parsedNews || !parsedNews?.news?.length || !parsedNews.timestamp)
|
||||
return null;
|
||||
|
||||
const now = new Date();
|
||||
const cacheExpiration = new Date(
|
||||
parsedNews.timestamp + NEWS_CACHE_CONFIG.ttl
|
||||
);
|
||||
if (now < cacheExpiration) return parsedNews.news;
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching cached news:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch news from remote source and cache it in localStorage
|
||||
* @returns {Promise<NewsItem[]|null>} - Array of news items
|
||||
*/
|
||||
async function fetchRemoteNews() {
|
||||
try {
|
||||
const latestArticleDateRef = await fetch(NEWS_CACHE_CONFIG.articles)
|
||||
.then((res) => {
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
`${res.status} - Failed to fetch remote news from ${NEWS_CACHE_CONFIG.articles}`
|
||||
);
|
||||
return res.text();
|
||||
})
|
||||
.then((text) => text?.split("\n")?.shift()?.trim())
|
||||
.catch((err) => {
|
||||
console.error(err.message);
|
||||
return null;
|
||||
});
|
||||
if (!latestArticleDateRef) return null;
|
||||
|
||||
const dataURL = `${NEWS_CACHE_CONFIG.announcementsDir}/${latestArticleDateRef}${latestArticleDateRef.endsWith(".json") ? "" : ".json"}`;
|
||||
/** @type {NewsItem[]|null} */
|
||||
const announcementData = await fetch(dataURL)
|
||||
.then((res) => {
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
`${res.status} - Failed to fetch remote news from ${dataURL}`
|
||||
);
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.message);
|
||||
return [];
|
||||
});
|
||||
|
||||
if (!announcementData?.length) return null;
|
||||
localStorage.setItem(
|
||||
NEWS_CACHE_CONFIG.cacheKey,
|
||||
JSON.stringify({
|
||||
news: announcementData,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
);
|
||||
|
||||
return announcementData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching remote news:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{news: NewsItem[], isLoading: boolean}}
|
||||
*/
|
||||
function useNewsItems() {
|
||||
const [news, setNews] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchAnnouncements() {
|
||||
try {
|
||||
const cachedNews = getCachedNews();
|
||||
if (cachedNews) return setNews(cachedNews);
|
||||
|
||||
const remoteNews = await fetchRemoteNews();
|
||||
if (remoteNews) return setNews(remoteNews);
|
||||
} catch (error) {
|
||||
console.error("Error fetching cached news:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchAnnouncements();
|
||||
}, []);
|
||||
|
||||
return { news, isLoading };
|
||||
}
|
26
frontend/src/pages/Main/Home/index.jsx
Normal file
26
frontend/src/pages/Main/Home/index.jsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import React from "react";
|
||||
import QuickLinks from "./QuickLinks";
|
||||
import ExploreFeatures from "./ExploreFeatures";
|
||||
import Updates from "./Updates";
|
||||
import Resources from "./Resources";
|
||||
import Checklist from "./Checklist";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div
|
||||
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-container w-full h-full"
|
||||
>
|
||||
<div className="w-full h-full flex flex-col items-center overflow-y-auto no-scroll">
|
||||
<div className="w-full max-w-[1200px] flex flex-col gap-y-[50px] p-4 pt-16 md:p-12">
|
||||
<Checklist />
|
||||
<QuickLinks />
|
||||
<ExploreFeatures />
|
||||
<Updates />
|
||||
<Resources />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,23 +1,24 @@
|
|||
import React from "react";
|
||||
import DefaultChatContainer from "@/components/DefaultChat";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import PasswordModal, { usePasswordModal } from "@/components/Modals/Password";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { FullScreenLoader } from "@/components/Preloader";
|
||||
import UserMenu from "@/components/UserMenu";
|
||||
import Home from "./Home";
|
||||
import DefaultChatContainer from "@/components/DefaultChat";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import Sidebar, { SidebarMobileHeader } from "@/components/Sidebar";
|
||||
import { userFromStorage } from "@/utils/request";
|
||||
|
||||
export default function Main() {
|
||||
const { loading, requiresAuth, mode } = usePasswordModal();
|
||||
|
||||
if (loading) return <FullScreenLoader />;
|
||||
if (requiresAuth !== false) {
|
||||
if (requiresAuth !== false)
|
||||
return <>{requiresAuth !== null && <PasswordModal mode={mode} />}</>;
|
||||
}
|
||||
|
||||
const user = userFromStorage();
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
|
||||
{!isMobile && <Sidebar />}
|
||||
<DefaultChatContainer />
|
||||
{!isMobile ? <Sidebar /> : <SidebarMobileHeader />}
|
||||
{!!user && user?.role !== "admin" ? <DefaultChatContainer /> : <Home />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { chatPrompt } from "@/utils/chat";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SystemPromptVariable from "@/models/systemPromptVariable";
|
||||
|
@ -11,6 +11,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
|||
const [availableVariables, setAvailableVariables] = useState([]);
|
||||
const [prompt, setPrompt] = useState(chatPrompt(workspace));
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const promptRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function setupVariableHighlighting() {
|
||||
|
@ -20,6 +21,18 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
|||
setupVariableHighlighting();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.hash === "#system-prompts") {
|
||||
setIsEditing(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && promptRef.current) {
|
||||
promptRef.current.focus();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
|
@ -73,6 +86,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
|||
</span>
|
||||
{isEditing ? (
|
||||
<textarea
|
||||
ref={promptRef}
|
||||
autoFocus={true}
|
||||
rows={5}
|
||||
onFocus={(e) => {
|
||||
|
|
|
@ -98,6 +98,36 @@ export default {
|
|||
},
|
||||
"file-picker": {
|
||||
hover: 'var(--theme-file-picker-hover)',
|
||||
},
|
||||
home: {
|
||||
text: 'var(--theme-home-text)',
|
||||
"text-secondary": 'var(--theme-home-text-secondary)',
|
||||
"bg-card": 'var(--theme-home-bg-card)',
|
||||
"bg-button": 'var(--theme-home-bg-button)',
|
||||
border: 'var(--theme-home-border)',
|
||||
"button-primary": 'var(--theme-home-button-primary)',
|
||||
"button-primary-hover": 'var(--theme-home-button-primary-hover)',
|
||||
"button-secondary": 'var(--theme-home-button-secondary)',
|
||||
"button-secondary-hover": 'var(--theme-home-button-secondary-hover)',
|
||||
"button-secondary-text": 'var(--theme-home-button-secondary-text)',
|
||||
"button-secondary-hover-text": 'var(--theme-home-button-secondary-hover-text)',
|
||||
"update-card-bg": 'var(--theme-home-update-card-bg)',
|
||||
"update-card-hover": 'var(--theme-home-update-card-hover)',
|
||||
"update-source": 'var(--theme-home-update-source)',
|
||||
},
|
||||
checklist: {
|
||||
"item-bg": 'var(--theme-checklist-item-bg)',
|
||||
"item-text": 'var(--theme-checklist-item-text)',
|
||||
"item-completed-bg": 'var(--theme-checklist-item-completed-bg)',
|
||||
"item-completed-text": 'var(--theme-checklist-item-completed-text)',
|
||||
"item-hover": 'var(--theme-checklist-item-hover)',
|
||||
"checkbox-border": 'var(--theme-checklist-checkbox-border)',
|
||||
"checkbox-fill": 'var(--theme-checklist-checkbox-fill)',
|
||||
"checkbox-text": 'var(--theme-checklist-checkbox-text)',
|
||||
"button-border": 'var(--theme-checklist-button-border)',
|
||||
"button-text": 'var(--theme-checklist-button-text)',
|
||||
"button-hover-bg": 'var(--theme-checklist-button-hover-bg)',
|
||||
"button-hover-border": 'var(--theme-checklist-button-hover-border)',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -197,6 +197,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.3.1", "@babel/runtime@^7.8.3":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
|
||||
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.24.7":
|
||||
version "7.24.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315"
|
||||
|
@ -231,6 +238,18 @@
|
|||
"@babel/helper-validator-identifier" "^7.24.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@emotion/is-prop-valid@^0.7.3":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc"
|
||||
integrity sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==
|
||||
dependencies:
|
||||
"@emotion/memoize" "0.7.1"
|
||||
|
||||
"@emotion/memoize@0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
|
||||
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
|
||||
|
||||
"@esbuild-plugins/node-globals-polyfill@^0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.1.1.tgz#a313ab3efbb2c17c8ce376aa216c627c9b40f9d7"
|
||||
|
@ -1195,6 +1214,15 @@ css-box-model@^1.2.0:
|
|||
dependencies:
|
||||
tiny-invariant "^1.0.6"
|
||||
|
||||
css-jss@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/css-jss/-/css-jss-10.10.0.tgz#bd51fbd255cc24597ac0f0f32368394794d37ef3"
|
||||
integrity sha512-YyMIS/LsSKEGXEaVJdjonWe18p4vXLo8CMA4FrW/kcaEyqdIGKCFXao31gbJddXEdIxSXFFURWrenBJPlKTgAA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "^10.10.0"
|
||||
jss-preset-default "^10.10.0"
|
||||
|
||||
css-line-break@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||
|
@ -1202,6 +1230,14 @@ css-line-break@^2.1.0:
|
|||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
css-vendor@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
|
||||
integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.3"
|
||||
is-in-browser "^1.0.2"
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
|
@ -2086,7 +2122,7 @@ highlight.js@^11.9.0:
|
|||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92"
|
||||
integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
hoist-non-react-statics@^3.2.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
|
@ -2108,6 +2144,11 @@ html2canvas@^1.2.0:
|
|||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
|
||||
hyphenate-style-name@^1.0.3:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
|
||||
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
|
||||
|
||||
i18next-browser-languagedetector@^7.2.1:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f"
|
||||
|
@ -2271,6 +2312,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
|||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
|
||||
integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==
|
||||
|
||||
is-map@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
|
||||
|
@ -2443,6 +2489,140 @@ json5@^2.2.3:
|
|||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jss-plugin-camel-case@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz#27ea159bab67eb4837fa0260204eb7925d4daa1c"
|
||||
integrity sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
hyphenate-style-name "^1.0.3"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-plugin-compose@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-compose/-/jss-plugin-compose-10.10.0.tgz#00d7a79adf7fcfe4927a792febdf0deceb0a7cd2"
|
||||
integrity sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-default-unit@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz#db3925cf6a07f8e1dd459549d9c8aadff9804293"
|
||||
integrity sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-plugin-expand@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-expand/-/jss-plugin-expand-10.10.0.tgz#5debd80554174ca2d9b9e38d85d4cb6f3e0393ab"
|
||||
integrity sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-plugin-extend@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-extend/-/jss-plugin-extend-10.10.0.tgz#94eb450847a8941777e77ea4533a579c1c578430"
|
||||
integrity sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-global@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz#1c55d3c35821fab67a538a38918292fc9c567efd"
|
||||
integrity sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-plugin-nested@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz#db872ed8925688806e77f1fc87f6e62264513219"
|
||||
integrity sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-props-sort@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz#67f4dd4c70830c126f4ec49b4b37ccddb680a5d7"
|
||||
integrity sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-plugin-rule-value-function@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz#7d99e3229e78a3712f78ba50ab342e881d26a24b"
|
||||
integrity sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-rule-value-observable@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.10.0.tgz#d17b28c4401156bbe4cd0c4a73a80aad70613e8b"
|
||||
integrity sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
||||
jss-plugin-template@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-template/-/jss-plugin-template-10.10.0.tgz#072cda74a94c91b02d3a895d9e2408fd978ce033"
|
||||
integrity sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
jss-plugin-vendor-prefixer@10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz#c01428ef5a89f2b128ec0af87a314d0c767931c7"
|
||||
integrity sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
css-vendor "^2.0.8"
|
||||
jss "10.10.0"
|
||||
|
||||
jss-preset-default@10.10.0, jss-preset-default@^10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-preset-default/-/jss-preset-default-10.10.0.tgz#c8209449a0f6d232526c2ba3a3a6ec69ee97e023"
|
||||
integrity sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
jss "10.10.0"
|
||||
jss-plugin-camel-case "10.10.0"
|
||||
jss-plugin-compose "10.10.0"
|
||||
jss-plugin-default-unit "10.10.0"
|
||||
jss-plugin-expand "10.10.0"
|
||||
jss-plugin-extend "10.10.0"
|
||||
jss-plugin-global "10.10.0"
|
||||
jss-plugin-nested "10.10.0"
|
||||
jss-plugin-props-sort "10.10.0"
|
||||
jss-plugin-rule-value-function "10.10.0"
|
||||
jss-plugin-rule-value-observable "10.10.0"
|
||||
jss-plugin-template "10.10.0"
|
||||
jss-plugin-vendor-prefixer "10.10.0"
|
||||
|
||||
jss@10.10.0, jss@^10.10.0:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss/-/jss-10.10.0.tgz#a75cc85b0108c7ac8c7b7d296c520a3e4fbc6ccc"
|
||||
integrity sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
csstype "^3.0.2"
|
||||
is-in-browser "^1.1.3"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
"jsx-ast-utils@^2.4.1 || ^3.0.0":
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
|
||||
|
@ -2924,7 +3104,7 @@ prettier@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
|
||||
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
|
||||
|
||||
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -2979,6 +3159,14 @@ react-beautiful-dnd@13.1.1:
|
|||
redux "^4.0.4"
|
||||
use-memo-one "^1.1.1"
|
||||
|
||||
react-confetti-explosion@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-confetti-explosion/-/react-confetti-explosion-2.1.2.tgz#dfbccf28985ce6938143ceb1023b9facfb0a5835"
|
||||
integrity sha512-4UzDFBajAGXmF9TSJoRMO2QOBCIXc66idTxH8l7Mkul48HLGtk+tMzK9HYDYsy7Zmw5sEGchi2fbn4AJUuLrZw==
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
react-jss "^10.9.2"
|
||||
|
||||
react-day-picker@^8.10.1:
|
||||
version "8.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.1.tgz#4762ec298865919b93ec09ba69621580835b8e80"
|
||||
|
@ -2991,6 +3179,11 @@ react-device-detect@^2.2.2:
|
|||
dependencies:
|
||||
ua-parser-js "^1.0.33"
|
||||
|
||||
react-display-name@^0.2.4:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/react-display-name/-/react-display-name-0.2.5.tgz#304c7cbfb59ee40389d436e1a822c17fe27936c6"
|
||||
integrity sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||
|
@ -3034,6 +3227,23 @@ react-is@^17.0.2:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-jss@^10.9.2:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-10.10.0.tgz#d08ab3257b0eed01e15d6d8275840055c279b0da"
|
||||
integrity sha512-WLiq84UYWqNBF6579/uprcIUnM1TSywYq6AIjKTTTG5ziJl9Uy+pwuvpN3apuyVwflMbD60PraeTKT7uWH9XEQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
"@emotion/is-prop-valid" "^0.7.3"
|
||||
css-jss "10.10.0"
|
||||
hoist-non-react-statics "^3.2.0"
|
||||
is-in-browser "^1.1.3"
|
||||
jss "10.10.0"
|
||||
jss-preset-default "10.10.0"
|
||||
prop-types "^15.6.0"
|
||||
shallow-equal "^1.2.0"
|
||||
theming "^3.3.0"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
react-loading-skeleton@^3.1.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.4.0.tgz#c71a3a17259d08e4064974aa0b07f150a09dfd57"
|
||||
|
@ -3321,6 +3531,11 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
|
|||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
shallow-equal@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
|
||||
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
|
@ -3486,6 +3701,11 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
symbol-observable@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
||||
synckit@^0.9.1:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88"
|
||||
|
@ -3689,6 +3909,16 @@ text-upper-case@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/text-upper-case/-/text-upper-case-1.0.2.tgz#d72eece80120d842754f05c721d28e1baa90ac51"
|
||||
integrity sha512-1YT5Y+gCOskZOGPVQW2xAGsSDlItHJoFVCVviObJAPv7Qdedf8sCHCKrz0hG15uzF8IQsiJz5PJZUMNwpuNmmA==
|
||||
|
||||
theming@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/theming/-/theming-3.3.0.tgz#dacabf04aa689edde35f1e1c117ec6de73fbf870"
|
||||
integrity sha512-u6l4qTJRDaWZsqa8JugaNt7Xd8PPl9+gonZaIe28vAhqgHMIG/DOyFPqiKN/gQLQYj05tHv+YQdNILL4zoiAVA==
|
||||
dependencies:
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
prop-types "^15.5.8"
|
||||
react-display-name "^0.2.4"
|
||||
tiny-warning "^1.0.2"
|
||||
|
||||
thenify-all@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||
|
@ -3708,6 +3938,11 @@ tiny-invariant@^1.0.6, tiny-invariant@^1.3.1:
|
|||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||
|
||||
tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue