mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-09-21 03:40:52 +02:00
WIP on iframe embed support
This commit is contained in:
parent
27381a612c
commit
083dc5bf1c
@ -3,7 +3,7 @@ import useSessionId from "@/hooks/useSessionId";
|
|||||||
import useOpenChat from "@/hooks/useOpen";
|
import useOpenChat from "@/hooks/useOpen";
|
||||||
import Head from "@/components/Head";
|
import Head from "@/components/Head";
|
||||||
import OpenButton from "@/components/OpenButton";
|
import OpenButton from "@/components/OpenButton";
|
||||||
import ChatWindow from "./components/ChatWindow";
|
import ChatWindow, { ChatWindowFull } from "./components/ChatWindow";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@ -16,6 +16,22 @@ export default function App() {
|
|||||||
}, [embedSettings.loaded]);
|
}, [embedSettings.loaded]);
|
||||||
|
|
||||||
if (!embedSettings.loaded) return null;
|
if (!embedSettings.loaded) return null;
|
||||||
|
|
||||||
|
if (embedSettings.iframe === 'enabled') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head />
|
||||||
|
<div className={`w-screen h-screen bg-red-500`}>
|
||||||
|
<ChatWindowFull
|
||||||
|
settings={embedSettings}
|
||||||
|
sessionId={sessionId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the default bubble chat if nothing is defined
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head />
|
<Head />
|
||||||
@ -25,11 +41,10 @@ export default function App() {
|
|||||||
width: isChatOpen ? 320 : "auto",
|
width: isChatOpen ? 320 : "auto",
|
||||||
height: isChatOpen ? "93vh" : "auto",
|
height: isChatOpen ? "93vh" : "auto",
|
||||||
}}
|
}}
|
||||||
className={`transition-all duration-300 ease-in-out ${
|
className={`transition-all duration-300 ease-in-out ${isChatOpen
|
||||||
isChatOpen
|
? "max-w-md p-4 bg-white rounded-lg border shadow-lg w-72"
|
||||||
? "max-w-md p-4 bg-white rounded-lg border shadow-lg w-72"
|
: "w-16 h-16 rounded-full"
|
||||||
: "w-16 h-16 rounded-full"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{isChatOpen && (
|
{isChatOpen && (
|
||||||
<ChatWindow
|
<ChatWindow
|
||||||
|
@ -51,6 +51,46 @@ export default function ChatWindowHeader({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ChatFullWindowHeader({
|
||||||
|
sessionId,
|
||||||
|
settings = {},
|
||||||
|
iconUrl = null,
|
||||||
|
setChatHistory,
|
||||||
|
}) {
|
||||||
|
const [showingOptions, setShowOptions] = useState(false);
|
||||||
|
|
||||||
|
const handleChatReset = async () => {
|
||||||
|
await ChatService.resetEmbedChatSession(settings, sessionId);
|
||||||
|
setChatHistory([]);
|
||||||
|
setShowOptions(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center relative">
|
||||||
|
<img
|
||||||
|
style={{ maxWidth: 100, maxHeight: 20 }}
|
||||||
|
src={iconUrl ?? AnythingLLMLogo}
|
||||||
|
alt={iconUrl ? "Brand" : "AnythingLLM Logo"}
|
||||||
|
/>
|
||||||
|
<div className="flex gap-x-1 items-center">
|
||||||
|
{settings.loaded && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowOptions(!showingOptions)}
|
||||||
|
className="hover:bg-gray-100 rounded-sm text-slate-800"
|
||||||
|
>
|
||||||
|
<DotsThreeOutlineVertical
|
||||||
|
size={18}
|
||||||
|
weight={!showingOptions ? "regular" : "fill"}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<OptionsMenu showing={showingOptions} resetChat={handleChatReset} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function OptionsMenu({ showing, resetChat }) {
|
function OptionsMenu({ showing, resetChat }) {
|
||||||
if (!showing) return null;
|
if (!showing) return null;
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ChatWindowHeader from "./Header";
|
import ChatWindowHeader, { ChatFullWindowHeader } from "./Header";
|
||||||
import SessionId from "../SessionId";
|
import SessionId from "../SessionId";
|
||||||
import useChatHistory from "@/hooks/chat/useChatHistory";
|
import useChatHistory from "@/hooks/chat/useChatHistory";
|
||||||
import ChatContainer from "./ChatContainer";
|
import ChatContainer from "./ChatContainer";
|
||||||
@ -30,6 +30,33 @@ export default function ChatWindow({ closeChat, settings, sessionId }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ChatWindowFull({ settings, sessionId }) {
|
||||||
|
const { chatHistory, setChatHistory, loading } = useChatHistory(
|
||||||
|
settings,
|
||||||
|
sessionId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) return null;
|
||||||
|
setEventDelegatorForCodeSnippets();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<ChatFullWindowHeader
|
||||||
|
sessionId={sessionId}
|
||||||
|
settings={settings}
|
||||||
|
iconUrl={settings.brandImageUrl}
|
||||||
|
setChatHistory={setChatHistory}
|
||||||
|
/>
|
||||||
|
{/* <ChatContainer
|
||||||
|
sessionId={sessionId}
|
||||||
|
settings={settings}
|
||||||
|
knownHistory={chatHistory}
|
||||||
|
/>
|
||||||
|
<SessionId /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Enables us to safely markdown and sanitize all responses without risk of injection
|
// Enables us to safely markdown and sanitize all responses without risk of injection
|
||||||
// but still be able to attach a handler to copy code snippets on all elements
|
// but still be able to attach a handler to copy code snippets on all elements
|
||||||
// that are code snippets.
|
// that are code snippets.
|
||||||
|
@ -17,6 +17,7 @@ const DEFAULT_SETTINGS = {
|
|||||||
|
|
||||||
// behaviors
|
// behaviors
|
||||||
openOnLoad: "off", // or "on"
|
openOnLoad: "off", // or "on"
|
||||||
|
iframe: null, // 'enabled' is the only valid option
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useGetScriptAttributes() {
|
export default function useGetScriptAttributes() {
|
||||||
|
@ -19,6 +19,8 @@ const { utilEndpoints } = require("./endpoints/utils");
|
|||||||
const { developerEndpoints } = require("./endpoints/api");
|
const { developerEndpoints } = require("./endpoints/api");
|
||||||
const { extensionEndpoints } = require("./endpoints/extensions");
|
const { extensionEndpoints } = require("./endpoints/extensions");
|
||||||
const { bootHTTP, bootSSL } = require("./utils/boot");
|
const { bootHTTP, bootSSL } = require("./utils/boot");
|
||||||
|
const { renderIFrameResponse } = require("./utils/embed/render");
|
||||||
|
const { validEmbedConfigUUID } = require("./utils/middleware/embedMiddleware");
|
||||||
const app = express();
|
const app = express();
|
||||||
const apiRouter = express.Router();
|
const apiRouter = express.Router();
|
||||||
const FILE_LIMIT = "3GB";
|
const FILE_LIMIT = "3GB";
|
||||||
@ -90,6 +92,10 @@ if (process.env.NODE_ENV !== "development") {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Named embedded to not collide with public folder /embed/ directory
|
||||||
|
// when in production
|
||||||
|
app.get("/embedded/:embedId", [validEmbedConfigUUID], renderIFrameResponse);
|
||||||
|
|
||||||
app.all("*", function (_, response) {
|
app.all("*", function (_, response) {
|
||||||
response.sendStatus(404);
|
response.sendStatus(404);
|
||||||
});
|
});
|
||||||
|
32
server/utils/embed/render.js
Normal file
32
server/utils/embed/render.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const HTML = ({ scriptLoc }) => `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<script
|
||||||
|
data-embed-id="example-uuid"
|
||||||
|
data-base-api-url='/api/embed'
|
||||||
|
data-iframe='enabled'
|
||||||
|
src="${scriptLoc}">
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
|
||||||
|
function renderIFrameResponse(request, response) {
|
||||||
|
const embed = response.locals.embedConfig;
|
||||||
|
const settings = {
|
||||||
|
scriptLoc:
|
||||||
|
process.env.NODE_ENV === 'production' ?
|
||||||
|
`/embed/anythingllm-chat-widget.min.js` :
|
||||||
|
'http://localhost:3080/dist/anythingllm-chat-widget.js', // Running yarn dev in embed folder
|
||||||
|
embedId: embed.id,
|
||||||
|
}
|
||||||
|
response.type('text/html');
|
||||||
|
response.header('Access-Control-Allow-Origin', '*');
|
||||||
|
response.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
|
||||||
|
response.header('Access-Control-Allow-Headers', 'Content-Type, X-Requested-With, Authorization');
|
||||||
|
response.send(HTML(settings)).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
renderIFrameResponse
|
||||||
|
}
|
@ -27,6 +27,19 @@ function setConnectionMeta(request, response, next) {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function validEmbedConfigUUID(request, response, next) {
|
||||||
|
const { embedId } = request.params;
|
||||||
|
|
||||||
|
const embed = await EmbedConfig.getWithWorkspace({ uuid: embedId });
|
||||||
|
if (!embed) {
|
||||||
|
response.sendStatus(404).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.locals.embedConfig = embed;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
async function validEmbedConfigId(request, response, next) {
|
async function validEmbedConfigId(request, response, next) {
|
||||||
const { embedId } = request.params;
|
const { embedId } = request.params;
|
||||||
|
|
||||||
@ -147,5 +160,6 @@ module.exports = {
|
|||||||
setConnectionMeta,
|
setConnectionMeta,
|
||||||
validEmbedConfig,
|
validEmbedConfig,
|
||||||
validEmbedConfigId,
|
validEmbedConfigId,
|
||||||
|
validEmbedConfigUUID,
|
||||||
canRespond,
|
canRespond,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user