From e76dc0f1caa38d8073397e8bf95de2e82f5be405 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Wed, 9 Apr 2025 13:33:25 -0700 Subject: [PATCH] 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 --- .github/workflows/build-and-push-image.yaml | 1 + .github/workflows/dev-build.yaml | 3 +- extras/support/announcements/2025-04-08.json | 26 ++ extras/support/announcements/assets/mcp.jpg | Bin 0 -> 18020 bytes extras/support/announcements/list.txt | 1 + frontend/package.json | 1 + .../PromptInput/AgentMenu/index.jsx | 22 +- .../SlashCommands/SlashPresets/index.jsx | 11 + frontend/src/index.css | 61 +++++ .../src/media/announcements/placeholder-1.png | Bin 0 -> 21559 bytes .../src/media/announcements/placeholder-2.png | Bin 0 -> 22570 bytes .../src/media/announcements/placeholder-3.png | Bin 0 -> 22174 bytes .../ChecklistItem/icons/SlashCommand.jsx | 28 ++ .../Home/Checklist/ChecklistItem/index.jsx | 81 ++++++ .../pages/Main/Home/Checklist/constants.js | 162 ++++++++++++ .../src/pages/Main/Home/Checklist/index.jsx | 213 ++++++++++++++++ .../pages/Main/Home/ExploreFeatures/index.jsx | 124 +++++++++ .../src/pages/Main/Home/QuickLinks/index.jsx | 98 +++++++ .../src/pages/Main/Home/Resources/index.jsx | 32 +++ .../src/pages/Main/Home/Updates/index.jsx | 225 +++++++++++++++++ frontend/src/pages/Main/Home/index.jsx | 26 ++ frontend/src/pages/Main/index.jsx | 17 +- .../ChatSettings/ChatPromptSettings/index.jsx | 16 +- frontend/tailwind.config.js | 30 +++ frontend/yarn.lock | 239 +++++++++++++++++- 25 files changed, 1400 insertions(+), 17 deletions(-) create mode 100644 extras/support/announcements/2025-04-08.json create mode 100644 extras/support/announcements/assets/mcp.jpg create mode 100644 extras/support/announcements/list.txt create mode 100644 frontend/src/media/announcements/placeholder-1.png create mode 100644 frontend/src/media/announcements/placeholder-2.png create mode 100644 frontend/src/media/announcements/placeholder-3.png create mode 100644 frontend/src/pages/Main/Home/Checklist/ChecklistItem/icons/SlashCommand.jsx create mode 100644 frontend/src/pages/Main/Home/Checklist/ChecklistItem/index.jsx create mode 100644 frontend/src/pages/Main/Home/Checklist/constants.js create mode 100644 frontend/src/pages/Main/Home/Checklist/index.jsx create mode 100644 frontend/src/pages/Main/Home/ExploreFeatures/index.jsx create mode 100644 frontend/src/pages/Main/Home/QuickLinks/index.jsx create mode 100644 frontend/src/pages/Main/Home/Resources/index.jsx create mode 100644 frontend/src/pages/Main/Home/Updates/index.jsx create mode 100644 frontend/src/pages/Main/Home/index.jsx diff --git a/.github/workflows/build-and-push-image.yaml b/.github/workflows/build-and-push-image.yaml index ad7c0af2..f111e2a0 100644 --- a/.github/workflows/build-and-push-image.yaml +++ b/.github/workflows/build-and-push-image.yaml @@ -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: diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index 7173dd42..7c6b0cc0 100644 --- a/.github/workflows/dev-build.yaml +++ b/.github/workflows/dev-build.yaml @@ -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: diff --git a/extras/support/announcements/2025-04-08.json b/extras/support/announcements/2025-04-08.json new file mode 100644 index 00000000..58535ecc --- /dev/null +++ b/extras/support/announcements/2025-04-08.json @@ -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" + } +] \ No newline at end of file diff --git a/extras/support/announcements/assets/mcp.jpg b/extras/support/announcements/assets/mcp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b0abd8a9cfd9b23e132f69bda1e2a485ab000f97 GIT binary patch literal 18020 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

NG0dG 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 z z-Zux-gH*J}lD@vWsgZO{$sqBN`g<2pLOc4oFqRy|Gn { + 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" > + )} + + ); +} diff --git a/frontend/src/pages/Main/Home/Checklist/constants.js b/frontend/src/pages/Main/Home/Checklist/constants.js new file mode 100644 index 00000000..84708970 --- /dev/null +++ b/frontend/src/pages/Main/Home/Checklist/constants.js @@ -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, + }, +]; diff --git a/frontend/src/pages/Main/Home/Checklist/index.jsx b/frontend/src/pages/Main/Home/Checklist/index.jsx new file mode 100644 index 00000000..7dde88c8 --- /dev/null +++ b/frontend/src/pages/Main/Home/Checklist/index.jsx @@ -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 ( +

+
+ {isCompleted && ( +
+ +
+ )} +
+

+ You're on your way to becoming an AnythingLLM expert! +

+
+
+ +
+
+
+

+ Getting Started +

+ {CHECKLIST_ITEMS.length - completedCount > 0 && ( +

+ {CHECKLIST_ITEMS.length - completedCount} tasks left +

+ )} +
+ +
+ +
+
+
+ {CHECKLIST_ITEMS.map((item) => ( + + ))} +
+
+ {showingNewWsModal && } + {selectedWorkspace && ( + { + setSelectedWorkspace(null); + hideManageWsModal(); + }} + /> + )} +
+ ); +} diff --git a/frontend/src/pages/Main/Home/ExploreFeatures/index.jsx b/frontend/src/pages/Main/Home/ExploreFeatures/index.jsx new file mode 100644 index 00000000..a1ca4330 --- /dev/null +++ b/frontend/src/pages/Main/Home/ExploreFeatures/index.jsx @@ -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 ( +
+

+ Explore more features +

+
+ + + +
+
+ ); +} + +function FeatureCard({ + title, + description, + primaryAction, + secondaryAction, + onPrimaryAction, + onSecondaryAction, + isNew, +}) { + return ( +
+
+

+ {title} +

+

{description}

+
+
+ + {secondaryAction && ( +
+ {isNew && ( +
+ New +
+ )} + +
+ )} +
+
+ ); +} diff --git a/frontend/src/pages/Main/Home/QuickLinks/index.jsx b/frontend/src/pages/Main/Home/QuickLinks/index.jsx new file mode 100644 index 00000000..eaa457d9 --- /dev/null +++ b/frontend/src/pages/Main/Home/QuickLinks/index.jsx @@ -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 ( +
+

+ Quick Links +

+
+ + + +
+ + {selectedWorkspace && ( + { + setSelectedWorkspace(null); + }} + /> + )} + + {showingNewWsModal && } +
+ ); +} diff --git a/frontend/src/pages/Main/Home/Resources/index.jsx b/frontend/src/pages/Main/Home/Resources/index.jsx new file mode 100644 index 00000000..44d6ec47 --- /dev/null +++ b/frontend/src/pages/Main/Home/Resources/index.jsx @@ -0,0 +1,32 @@ +import paths from "@/utils/paths"; +import { ArrowCircleUpRight } from "@phosphor-icons/react"; + +export default function Resources() { + return ( +
+

+ Resources +

+ +
+ ); +} diff --git a/frontend/src/pages/Main/Home/Updates/index.jsx b/frontend/src/pages/Main/Home/Updates/index.jsx new file mode 100644 index 00000000..0732ea1f --- /dev/null +++ b/frontend/src/pages/Main/Home/Updates/index.jsx @@ -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 ( +
+

+ Updates & Announcements +

+
+ {news.map((item, index) => ( + + ))} +
+
+ ); +} + +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 ( + +
+ {title} (e.target.src = placeHolderImage)} + className="w-[80px] h-[80px] rounded-lg flex-shrink-0 object-cover" + /> +
+
+

{source}

+ {isExternalLink && ( + + )} +
+

{title}

+

+ {subtitle} +

+
+ {author} + {date ?? "Recently"} +
+
+
+ + ); +} + +/** + * 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} - 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 }; +} diff --git a/frontend/src/pages/Main/Home/index.jsx b/frontend/src/pages/Main/Home/index.jsx new file mode 100644 index 00000000..63cd618f --- /dev/null +++ b/frontend/src/pages/Main/Home/index.jsx @@ -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 ( +
+
+
+ + + + + +
+
+
+ ); +} diff --git a/frontend/src/pages/Main/index.jsx b/frontend/src/pages/Main/index.jsx index 5a802b6f..12d4b933 100644 --- a/frontend/src/pages/Main/index.jsx +++ b/frontend/src/pages/Main/index.jsx @@ -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 ; - if (requiresAuth !== false) { + if (requiresAuth !== false) return <>{requiresAuth !== null && }; - } + const user = userFromStorage(); return (
- {!isMobile && } - + {!isMobile ? : } + {!!user && user?.role !== "admin" ? : }
); } diff --git a/frontend/src/pages/WorkspaceSettings/ChatSettings/ChatPromptSettings/index.jsx b/frontend/src/pages/WorkspaceSettings/ChatSettings/ChatPromptSettings/index.jsx index f5873594..5b9ca404 100644 --- a/frontend/src/pages/WorkspaceSettings/ChatSettings/ChatPromptSettings/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/ChatSettings/ChatPromptSettings/index.jsx @@ -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 (
@@ -73,6 +86,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) { {isEditing ? (