diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 143c78da..744262fb 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -614,6 +614,7 @@ "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", "AI & MCP": "AI & MCP", "AI": "AI", + "AI settings": "AI settings", "MCP": "MCP", "Model Context Protocol (MCP)": "Model Context Protocol (MCP)", "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.", diff --git a/apps/client/src/components/settings/settings-sidebar.tsx b/apps/client/src/components/settings/settings-sidebar.tsx index 70f20782..1c49f918 100644 --- a/apps/client/src/components/settings/settings-sidebar.tsx +++ b/apps/client/src/components/settings/settings-sidebar.tsx @@ -113,7 +113,7 @@ const groupedData: DataGroup[] = [ showDisabledInNonEE: true, }, { - label: "AI", + label: "AI settings", icon: IconSparkles, path: "/settings/ai", isAdmin: true, diff --git a/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx b/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx index ab02d770..9ff1b7ac 100644 --- a/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx +++ b/apps/client/src/ee/ai/components/editor/ai-menu/ai-menu.tsx @@ -1,5 +1,5 @@ import { Editor } from "@tiptap/react"; -import { ActionIcon, TextInput, Tooltip } from "@mantine/core"; +import { ActionIcon, TextInput } from "@mantine/core"; import { useDebouncedCallback, useMediaQuery } from "@mantine/hooks"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; @@ -14,7 +14,7 @@ import { ResultPreview } from "./result-preview.tsx"; import classes from "./ai-menu.module.css"; import { marked } from "marked"; import { DOMSerializer } from "@tiptap/pm/model"; -import { htmlToMarkdown } from "@docmost/editor-ext"; +import { copyToClipboard, htmlToMarkdown } from "@docmost/editor-ext"; import { useLocation } from "react-router-dom"; interface EditorAiMenuProps { @@ -110,6 +110,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { setOutput((output) => output + chunk.content); }, onComplete: () => { + setPrompt(""); setIsLoading(false); setActiveCommandSet("result"); }, @@ -146,13 +147,18 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { } const html = (marked.parse(output) as string).trim(); - // Strip

wrapper for single-paragraph output to preserve inline context - const content = + const isSingleParagraph = html.startsWith("

") && html.endsWith("

") && - html.lastIndexOf("

") === 0 - ? html.slice(3, -4) - : html; + html.lastIndexOf("

") === 0; + + // Strip

wrapper for single-paragraph output to preserve inline context, + // then decode HTML entities via DOMParser since TipTap would otherwise + // treat the tagless string as plain text and insert entities literally. + const content = isSingleParagraph + ? new DOMParser().parseFromString(html.slice(3, -4), "text/html") + .body.innerHTML + : html; chain.insertContent(content).run(); @@ -169,7 +175,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => { return setShowAiMenu(false); } if (item.id === "result-copy") { - navigator.clipboard.writeText(output); + copyToClipboard(output); return setShowAiMenu(false); } diff --git a/apps/client/src/ee/ai/components/mcp-settings.tsx b/apps/client/src/ee/ai/components/mcp-settings.tsx index 396e74ff..d39d285b 100644 --- a/apps/client/src/ee/ai/components/mcp-settings.tsx +++ b/apps/client/src/ee/ai/components/mcp-settings.tsx @@ -27,7 +27,7 @@ export default function McpSettings() { const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp); const hasAccess = useIsCloudEE(); - const mcpUrl = `${getAppUrl()}/api/mcp`; + const mcpUrl = `${getAppUrl()}/mcp`; const handleChange = async (event: React.ChangeEvent) => { const value = event.currentTarget.checked; diff --git a/apps/client/src/ee/ai/pages/ai-settings.tsx b/apps/client/src/ee/ai/pages/ai-settings.tsx index d0aaeafe..53fa9a87 100644 --- a/apps/client/src/ee/ai/pages/ai-settings.tsx +++ b/apps/client/src/ee/ai/pages/ai-settings.tsx @@ -37,9 +37,9 @@ export default function AiSettings() { return ( <> - AI - {getAppName()} + AI settings - {getAppName()} - + diff --git a/apps/client/src/ee/api-key/pages/user-api-keys.tsx b/apps/client/src/ee/api-key/pages/user-api-keys.tsx index 9e7c3446..b58bff7c 100644 --- a/apps/client/src/ee/api-key/pages/user-api-keys.tsx +++ b/apps/client/src/ee/api-key/pages/user-api-keys.tsx @@ -77,7 +77,7 @@ export default function UserApiKeys() { {t("MCP server URL:")}{" "} - {`${getAppUrl()}/api/mcp`} + {`${getAppUrl()}/mcp`} diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index d4c8dc17..fca457e5 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -36,7 +36,7 @@ async function bootstrap() { app.useLogger(app.get(PinoLogger)); app.setGlobalPrefix('api', { - exclude: ['robots.txt', 'share/:shareId/p/:pageSlug'], + exclude: ['robots.txt', 'share/:shareId/p/:pageSlug', 'mcp'], }); const reflector = app.get(Reflector);