diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 8cfbe126e8..d56836ef6b 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -13,6 +13,11 @@ export const experimentalFeatures = [ id: "new-layout", name: t("experimental_features.new_layout_name"), description: t("experimental_features.new_layout_description"), + }, + { + id: "llm", + name: t("experimental_features.llm_name"), + description: t("experimental_features.llm_description"), } ] as const satisfies ExperimentalFeature[]; diff --git a/apps/client/src/services/note_types.ts b/apps/client/src/services/note_types.ts index 84d4997d0f..c99f04ae81 100644 --- a/apps/client/src/services/note_types.ts +++ b/apps/client/src/services/note_types.ts @@ -1,6 +1,7 @@ import type { NoteType } from "../entities/fnote.js"; import type { MenuCommandItem, MenuItem, MenuItemBadge, MenuSeparatorItem } from "../menus/context_menu.js"; import type { TreeCommandNames } from "../menus/tree_context_menu.js"; +import { isExperimentalFeatureEnabled } from "./experimental_features.js"; import froca from "./froca.js"; import { t } from "./i18n.js"; import server from "./server.js"; @@ -41,7 +42,7 @@ export const NOTE_TYPES: NoteTypeMapping[] = [ { type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), icon: "bxs-network-chart" }, // Misc note types - { type: "llmChat", mime: "application/json", title: t("note_types.llm-chat"), icon: "bx-message-square-dots" }, + { type: "llmChat", mime: "application/json", title: t("note_types.llm-chat"), icon: "bx-message-square-dots", isBeta: true }, { type: "render", mime: "", title: t("note_types.render-note"), icon: "bx-extension" }, { type: "search", title: t("note_types.saved-search"), icon: "bx-file-find", static: true }, { type: "webView", mime: "", title: t("note_types.web-view"), icon: "bx-globe-alt" }, @@ -93,6 +94,7 @@ async function getNoteTypeItems(command?: TreeCommandNames) { function getBlankNoteTypes(command?: TreeCommandNames): MenuItem[] { return NOTE_TYPES .filter((nt) => !nt.reserved && nt.type !== "book") + .filter((nt) => nt.type !== "llmChat" || isExperimentalFeatureEnabled("llm")) .map((nt) => { const menuItem: MenuCommandItem = { title: nt.title, diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 576da1a194..630868668c 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1157,7 +1157,9 @@ "title": "Experimental Options", "disclaimer": "These options are experimental and may cause instability. Use with caution.", "new_layout_name": "New Layout", - "new_layout_description": "Try out the new layout for a more modern look and improved usability. Subject to heavy change in the upcoming releases." + "new_layout_description": "Try out the new layout for a more modern look and improved usability. Subject to heavy change in the upcoming releases.", + "llm_name": "AI / LLM Chat", + "llm_description": "Enable the AI chat sidebar and LLM chat notes powered by large language models." }, "fonts": { "theme_defined": "Theme defined", diff --git a/apps/client/src/widgets/launch_bar/LauncherContainer.tsx b/apps/client/src/widgets/launch_bar/LauncherContainer.tsx index d53ec6fb88..d5d443084b 100644 --- a/apps/client/src/widgets/launch_bar/LauncherContainer.tsx +++ b/apps/client/src/widgets/launch_bar/LauncherContainer.tsx @@ -1,6 +1,7 @@ import { useCallback, useLayoutEffect, useState } from "preact/hooks"; import FNote from "../../entities/fnote"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import froca from "../../services/froca"; import { isDesktop, isMobile } from "../../services/utils"; import TabSwitcher from "../mobile_widgets/TabSwitcher"; @@ -100,7 +101,7 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) { case "mobileTabSwitcher": return ; case "sidebarChat": - return ; + return isExperimentalFeatureEnabled("llm") ? : undefined; default: console.warn(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`); } diff --git a/apps/client/src/widgets/layout/NoteTypeSwitcher.tsx b/apps/client/src/widgets/layout/NoteTypeSwitcher.tsx index e345249add..cf685828aa 100644 --- a/apps/client/src/widgets/layout/NoteTypeSwitcher.tsx +++ b/apps/client/src/widgets/layout/NoteTypeSwitcher.tsx @@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from "preact/hooks"; import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types"; @@ -28,6 +29,7 @@ export default function NoteTypeSwitcher() { const restNoteTypes: NoteTypeMapping[] = []; for (const noteType of NOTE_TYPES) { if (noteType.reserved || noteType.static || noteType.type === "book") continue; + if (noteType.type === "llmChat" && !isExperimentalFeatureEnabled("llm")) continue; if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) { pinnedNoteTypes.push(noteType); } else { diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 5dbd0d29cb..4fb2358d30 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -7,6 +7,7 @@ import branches from "../../services/branches"; import dialog from "../../services/dialog"; import { getAvailableLocales, t } from "../../services/i18n"; import mime_types from "../../services/mime_types"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { NOTE_TYPES } from "../../services/note_types"; import protected_session from "../../services/protected_session"; import server from "../../services/server"; @@ -72,7 +73,7 @@ export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note noCodeNotes?: boolean; }) { const mimeTypes = useMimeTypes(); - const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); + const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static && (nt.type !== "llmChat" || isExperimentalFeatureEnabled("llm"))), []); const changeNoteType = useCallback(async (type: NoteType, mime?: string) => { if (!note || (type === currentNoteType && mime === currentNoteMime)) { return; diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index 88bd3d9288..b758cea301 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import appContext from "../../components/app_context"; import { WidgetsByParent } from "../../services/bundle"; +import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; @@ -94,7 +95,7 @@ function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) { }, { el: , - enabled: noteType !== "llmChat", + enabled: noteType !== "llmChat" && isExperimentalFeatureEnabled("llm"), position: 1000 }, ...widgetsByParent.getLegacyWidgets("right-pane").map((widget) => ({ diff --git a/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts b/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts index 68b83f1a2c..a6931299e6 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts +++ b/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts @@ -192,6 +192,7 @@ export function useLlmChat( const clearMessages = useCallback(() => { setMessages([]); + setLastPromptTokens(0); }, [setMessages]); const handleSubmit = useCallback(async (e: Event) => {