mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
* enhance ai menu
* remove api prefix from mcp
This commit is contained in:
@@ -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 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 & MCP": "AI & MCP",
|
||||||
"AI": "AI",
|
"AI": "AI",
|
||||||
|
"AI settings": "AI settings",
|
||||||
"MCP": "MCP",
|
"MCP": "MCP",
|
||||||
"Model Context Protocol (MCP)": "Model Context Protocol (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.",
|
"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.",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const groupedData: DataGroup[] = [
|
|||||||
showDisabledInNonEE: true,
|
showDisabledInNonEE: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "AI",
|
label: "AI settings",
|
||||||
icon: IconSparkles,
|
icon: IconSparkles,
|
||||||
path: "/settings/ai",
|
path: "/settings/ai",
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Editor } from "@tiptap/react";
|
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 { useDebouncedCallback, useMediaQuery } from "@mantine/hooks";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
@@ -14,7 +14,7 @@ import { ResultPreview } from "./result-preview.tsx";
|
|||||||
import classes from "./ai-menu.module.css";
|
import classes from "./ai-menu.module.css";
|
||||||
import { marked } from "marked";
|
import { marked } from "marked";
|
||||||
import { DOMSerializer } from "@tiptap/pm/model";
|
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";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
interface EditorAiMenuProps {
|
interface EditorAiMenuProps {
|
||||||
@@ -110,6 +110,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => {
|
|||||||
setOutput((output) => output + chunk.content);
|
setOutput((output) => output + chunk.content);
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
|
setPrompt("");
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setActiveCommandSet("result");
|
setActiveCommandSet("result");
|
||||||
},
|
},
|
||||||
@@ -146,13 +147,18 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const html = (marked.parse(output) as string).trim();
|
const html = (marked.parse(output) as string).trim();
|
||||||
// Strip <p> wrapper for single-paragraph output to preserve inline context
|
const isSingleParagraph =
|
||||||
const content =
|
|
||||||
html.startsWith("<p>") &&
|
html.startsWith("<p>") &&
|
||||||
html.endsWith("</p>") &&
|
html.endsWith("</p>") &&
|
||||||
html.lastIndexOf("<p>") === 0
|
html.lastIndexOf("<p>") === 0;
|
||||||
? html.slice(3, -4)
|
|
||||||
: html;
|
// Strip <p> 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();
|
chain.insertContent(content).run();
|
||||||
|
|
||||||
@@ -169,7 +175,7 @@ const EditorAiMenu = ({ editor }: EditorAiMenuProps): JSX.Element | null => {
|
|||||||
return setShowAiMenu(false);
|
return setShowAiMenu(false);
|
||||||
}
|
}
|
||||||
if (item.id === "result-copy") {
|
if (item.id === "result-copy") {
|
||||||
navigator.clipboard.writeText(output);
|
copyToClipboard(output);
|
||||||
|
|
||||||
return setShowAiMenu(false);
|
return setShowAiMenu(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function McpSettings() {
|
|||||||
const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp);
|
const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp);
|
||||||
const hasAccess = useIsCloudEE();
|
const hasAccess = useIsCloudEE();
|
||||||
|
|
||||||
const mcpUrl = `${getAppUrl()}/api/mcp`;
|
const mcpUrl = `${getAppUrl()}/mcp`;
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.currentTarget.checked;
|
const value = event.currentTarget.checked;
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ export default function AiSettings() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>AI - {getAppName()}</title>
|
<title>AI settings - {getAppName()}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<SettingsTitle title={t("AI")} />
|
<SettingsTitle title={t("AI settings")} />
|
||||||
|
|
||||||
<Tabs color="dark" value={activeTab} onChange={handleTabChange}>
|
<Tabs color="dark" value={activeTab} onChange={handleTabChange}>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default function UserApiKeys() {
|
|||||||
<Text size="sm" mt={4}>
|
<Text size="sm" mt={4}>
|
||||||
{t("MCP server URL:")}{" "}
|
{t("MCP server URL:")}{" "}
|
||||||
<Text size="sm" fw={500} span ff="monospace">
|
<Text size="sm" fw={500} span ff="monospace">
|
||||||
{`${getAppUrl()}/api/mcp`}
|
{`${getAppUrl()}/mcp`}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ async function bootstrap() {
|
|||||||
app.useLogger(app.get(PinoLogger));
|
app.useLogger(app.get(PinoLogger));
|
||||||
|
|
||||||
app.setGlobalPrefix('api', {
|
app.setGlobalPrefix('api', {
|
||||||
exclude: ['robots.txt', 'share/:shareId/p/:pageSlug'],
|
exclude: ['robots.txt', 'share/:shareId/p/:pageSlug', 'mcp'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const reflector = app.get(Reflector);
|
const reflector = app.get(Reflector);
|
||||||
|
|||||||
Reference in New Issue
Block a user