From 5b79cbed8934f351365ab53696c7c9aa079f3887 Mon Sep 17 00:00:00 2001 From: swve Date: Thu, 11 Jan 2024 01:39:43 +0100 Subject: [PATCH] feat: init ai editor writer and continue writing feature --- apps/api/src/services/ai/ai.py | 2 +- apps/api/src/services/ai/base.py | 3 +- .../activity/[activityuuid]/edit/page.tsx | 11 +- apps/web/components/AI/AIActivityAsk.tsx | 10 +- .../Contexts/AI/AIChatBotContext.tsx | 4 +- .../Contexts/AI/AIEditorContext.tsx | 82 +++++ .../Activities/DynamicCanva/DynamicCanva.tsx | 2 +- .../DynamicCanva/Elements/AICanvaToolkit.tsx | 6 +- .../Objects/Editor/AI/AIEditorToolkit.tsx | 308 ++++++++++++++++++ apps/web/components/Objects/Editor/Editor.tsx | 41 ++- .../Objects/Editor/Toolbar/ToolbarButtons.tsx | 6 +- .../public/learnhouse_ai_simple_colored.png | Bin 0 -> 29183 bytes 12 files changed, 446 insertions(+), 29 deletions(-) create mode 100644 apps/web/components/Contexts/AI/AIEditorContext.tsx create mode 100644 apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx create mode 100644 apps/web/public/learnhouse_ai_simple_colored.png diff --git a/apps/api/src/services/ai/ai.py b/apps/api/src/services/ai/ai.py index bc776962..e0e21e3e 100644 --- a/apps/api/src/services/ai/ai.py +++ b/apps/api/src/services/ai/ai.py @@ -117,7 +117,7 @@ def ai_send_activity_chat_message( ) # Get Activity Content Blocks - content = activity.content + content = activity.content # Serialize Activity Content Blocks to a text comprehensible by the AI structured = structure_activity_content_by_type(content) diff --git a/apps/api/src/services/ai/base.py b/apps/api/src/services/ai/base.py index 02674e92..f2356fa2 100644 --- a/apps/api/src/services/ai/base.py +++ b/apps/api/src/services/ai/base.py @@ -59,7 +59,7 @@ def ask_ai( memory_key = "history" memory = AgentTokenBufferMemory( - memory_key=memory_key, llm=llm, chat_memory=message_history + memory_key=memory_key, llm=llm, chat_memory=message_history, max_tokens=1000 ) system_message = SystemMessage(content=(message_for_the_prompt)) @@ -77,6 +77,7 @@ def ask_ai( memory=memory, verbose=True, return_intermediate_steps=True, + handle_parsing_errors=True, ) return agent_executor({"input": question}) diff --git a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx index 672002a1..f648cd84 100644 --- a/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx +++ b/apps/web/app/editor/course/[courseid]/activity/[activityuuid]/edit/page.tsx @@ -8,6 +8,8 @@ import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshToke import { getOrganizationContextInfo, getOrganizationContextInfoWithId } from "@services/organizations/orgs"; import SessionProvider from "@components/Contexts/SessionContext"; import EditorOptionsProvider from "@components/Contexts/Editor/EditorContext"; +import AIChatBotProvider from "@components/Contexts/AI/AIChatBotContext"; +import AIEditorProvider from "@components/Contexts/AI/AIEditorContext"; type MetadataProps = { params: { orgslug: string, courseid: string, activityid: string }; @@ -36,13 +38,14 @@ const EditActivity = async (params: any) => { const courseInfo = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null) const activity = await getActivityWithAuthHeader(activityuuid, { revalidate: 0, tags: ['activities'] }, access_token ? access_token : null) const org = await getOrganizationContextInfoWithId(courseInfo.org_id, { revalidate: 1800, tags: ['organizations'] }); - console.log('courseInfo', courseInfo) return ( - - - + + + + + ); } diff --git a/apps/web/components/AI/AIActivityAsk.tsx b/apps/web/components/AI/AIActivityAsk.tsx index 6ae62c49..c30f315d 100644 --- a/apps/web/components/AI/AIActivityAsk.tsx +++ b/apps/web/components/AI/AIActivityAsk.tsx @@ -31,10 +31,10 @@ function AIActivityAsk(props: AIActivityAskProps) { style={{ background: 'conic-gradient(from 32deg at 53.75% 50%, rgb(35, 40, 93) 4deg, rgba(20, 0, 52, 0.95) 59deg, rgba(164, 45, 238, 0.88) 281deg)', }} - className="rounded-full px-5 drop-shadow-md flex items-center space-x-1 p-2.5 text-sm text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out hover:scale-105"> + className="rounded-full px-5 drop-shadow-md flex items-center space-x-1.5 p-2.5 text-sm text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out hover:scale-105"> {" "} - + {" "} Ask AI @@ -59,8 +59,8 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) { // TODO : come up with a better way to handle this const inputClass = aiChatBotState.isWaitingForResponse - ? 'ring-1 ring-inset ring-white/10 bg-transparent w-full rounded-lg outline-none px-4 py-2 text-white text-sm placeholder:text-white/30 opacity-30 ' - : 'ring-1 ring-inset ring-white/10 bg-transparent w-full rounded-lg outline-none px-4 py-2 text-white text-sm placeholder:text-white/30'; + ? 'ring-1 ring-inset ring-white/10 bg-gray-950/40 w-full rounded-lg outline-none px-4 py-2 text-white text-sm placeholder:text-white/30 opacity-30 ' + : 'ring-1 ring-inset ring-white/10 bg-gray-950/40 w-full rounded-lg outline-none px-4 py-2 text-white text-sm placeholder:text-white/30'; useEffect(() => { if (aiChatBotState.isModalOpen) { @@ -141,7 +141,7 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) {
- + AI
diff --git a/apps/web/components/Contexts/AI/AIChatBotContext.tsx b/apps/web/components/Contexts/AI/AIChatBotContext.tsx index e7bd9cc3..d39d93e7 100644 --- a/apps/web/components/Contexts/AI/AIChatBotContext.tsx +++ b/apps/web/components/Contexts/AI/AIChatBotContext.tsx @@ -1,8 +1,6 @@ +'use client'; import { AIMessage } from '@components/AI/AIActivityAsk'; import React, { createContext, useContext, useReducer } from 'react' - - - export const AIChatBotContext = createContext(null) as any; export const AIChatBotDispatchContext = createContext(null) as any; diff --git a/apps/web/components/Contexts/AI/AIEditorContext.tsx b/apps/web/components/Contexts/AI/AIEditorContext.tsx new file mode 100644 index 00000000..aa314032 --- /dev/null +++ b/apps/web/components/Contexts/AI/AIEditorContext.tsx @@ -0,0 +1,82 @@ +'use client'; +import { AIMessage } from '@components/AI/AIActivityAsk'; +import React, { createContext, useContext, useReducer } from 'react' +export const AIEditorContext = createContext(null) as any; +export const AIEditorDispatchContext = createContext(null) as any; + +export type AIEditorStateTypes = { + + messages: AIMessage[], + isModalOpen: boolean, + isFeedbackModalOpen: boolean, + aichat_uuid: string, + isWaitingForResponse: boolean, + chatInputValue: string, + selectedTool: 'Writer' | 'ContinueWriting' | 'MakeLonger' | 'GenerateQuiz' | 'Translate' + isUserInputEnabled: boolean +} + +function AIEditorProvider({ children }: { children: React.ReactNode }) { + const [aIEditorState, dispatchAIEditor] = useReducer(aIEditorReducer, + { + messages: [] as AIMessage[], + isModalOpen: false, + isFeedbackModalOpen: false, + aichat_uuid: null, + isWaitingForResponse: false, + chatInputValue: '', + selectedTool: 'Writer', + isUserInputEnabled: true + } + ); + return ( + + + {children} + + + ) +} + +export default AIEditorProvider + +export function useAIEditor() { + return useContext(AIEditorContext); +} + +export function useAIEditorDispatch() { + return useContext(AIEditorDispatchContext); +} + +function aIEditorReducer(state: any, action: any) { + switch (action.type) { + case 'setMessages': + return { ...state, messages: action.payload }; + case 'addMessage': + return { ...state, messages: [...state.messages, action.payload] }; + case 'setIsModalOpen': + return { ...state, isModalOpen: true }; + case 'setIsModalClose': + return { ...state, isModalOpen: false }; + case 'setAichat_uuid': + return { ...state, aichat_uuid: action.payload }; + case 'setIsWaitingForResponse': + return { ...state, isWaitingForResponse: true }; + case 'setIsNoLongerWaitingForResponse': + return { ...state, isWaitingForResponse: false }; + case 'setChatInputValue': + return { ...state, chatInputValue: action.payload }; + case 'setSelectedTool': + return { ...state, selectedTool: action.payload }; + case 'setIsFeedbackModalOpen': + return { ...state, isFeedbackModalOpen: true }; + case 'setIsFeedbackModalClose': + return { ...state, isFeedbackModalOpen: false }; + case 'setIsUserInputEnabled': + return { ...state, isUserInputEnabled: action.payload }; + + + default: + throw new Error(`Unhandled action type: ${action.type}`) + } +} \ No newline at end of file diff --git a/apps/web/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx b/apps/web/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx index 8d2546f3..d4685919 100644 --- a/apps/web/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx +++ b/apps/web/components/Objects/Activities/DynamicCanva/DynamicCanva.tsx @@ -101,7 +101,7 @@ function Canva(props: Editor) { - + diff --git a/apps/web/components/Objects/Activities/DynamicCanva/Elements/AICanvaToolkit.tsx b/apps/web/components/Objects/Activities/DynamicCanva/Elements/AICanvaToolkit.tsx index 5619bae9..4d53d921 100644 --- a/apps/web/components/Objects/Activities/DynamicCanva/Elements/AICanvaToolkit.tsx +++ b/apps/web/components/Objects/Activities/DynamicCanva/Elements/AICanvaToolkit.tsx @@ -21,7 +21,7 @@ function AICanvaToolkit(props: AICanvaToolkitProps) {
-
AI
+
AI
@@ -39,15 +39,13 @@ function AICanvaToolkit(props: AICanvaToolkitProps) { function AIActionButton(props: { editor: Editor, label: string, activity: any }) { const dispatchAIChatBot = useAIChatBotDispatch() as any; const aiChatBotState = useAIChatBot() as AIChatBotStateTypes; - const [aichat_uuid, setAichat_uuid] = React.useState(''); async function handleAction(label: string) { const selection = getTipTapEditorSelectedText(); const prompt = getPrompt(label, selection); dispatchAIChatBot({ type: 'setIsModalOpen' }); await sendMessage(prompt); - - + } const getTipTapEditorSelectedText = () => { diff --git a/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx new file mode 100644 index 00000000..46a01045 --- /dev/null +++ b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx @@ -0,0 +1,308 @@ +import React from 'react' +import learnhouseAI_icon from "public/learnhouse_ai_simple.png"; +import { motion, AnimatePresence } from 'framer-motion'; +import Image from 'next/image'; +import { BetweenHorizontalStart, FastForward, Feather, FileStack, HelpCircle, Languages, MessageCircle, MoreVertical, Pen, X } from 'lucide-react'; +import { Editor } from '@tiptap/react'; +import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@components/Contexts/AI/AIChatBotContext'; +import { AIEditorStateTypes, useAIEditor, useAIEditorDispatch } from '@components/Contexts/AI/AIEditorContext'; +import { sendActivityAIChatMessage, startActivityAIChatSession } from '@services/ai/ai'; + +type AIEditorToolkitProps = { + editor: Editor, + activity: any +} + +type AIPromptsLabels = { + label: 'Writer' | 'ContinueWriting' | 'MakeLonger' | 'GenerateQuiz' | 'Translate', + selection: string + +} + +function AIEditorToolkit(props: AIEditorToolkitProps) { + const dispatchAIEditor = useAIEditorDispatch() as any; + const aiEditorState = useAIEditor() as AIEditorStateTypes; + + + return ( + + {aiEditorState.isModalOpen && + <> + {aiEditorState.isFeedbackModalOpen && } +
+
+
+
+ +
AI Editor
+ +
+
+
+ + + + + +
+
+ Promise.all([dispatchAIEditor({ type: 'setIsModalClose' }), dispatchAIEditor({ type: 'setIsFeedbackModalClose' })])} size={20} className='text-white/50 hover:cursor-pointer bg-white/10 p-1 rounded-full items-center' /> +
+
+
+
} +
+ ) +} + + +const UserFeedbackModal = (props: AIEditorToolkitProps) => { + const dispatchAIEditor = useAIEditorDispatch() as any; + const aiEditorState = useAIEditor() as AIEditorStateTypes; + + const handleChange = async (event: React.ChangeEvent) => { + await dispatchAIEditor({ type: 'setChatInputValue', payload: event.currentTarget.value }); + + } + + const sendReqWithMessage = async (message: string) => { + if (aiEditorState.aichat_uuid) { + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'user', message: message, type: 'user' } }); + await dispatchAIEditor({ type: 'setIsWaitingForResponse' }); + const response = await sendActivityAIChatMessage(message, aiEditorState.aichat_uuid, props.activity.activity_uuid) + await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); + await dispatchAIEditor({ type: 'setChatInputValue', payload: '' }); + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.message, type: 'ai' } }); + return response.message; + + } else { + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'user', message: message, type: 'user' } }); + await dispatchAIEditor({ type: 'setIsWaitingForResponse' }); + const response = await startActivityAIChatSession(message, props.activity.activity_uuid) + await dispatchAIEditor({ type: 'setAichat_uuid', payload: response.aichat_uuid }); + await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); + await dispatchAIEditor({ type: 'setChatInputValue', payload: '' }); + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.message, type: 'ai' } }); + return response.message; + } + } + + const handleKeyPress = async (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + await handleOperation(aiEditorState.selectedTool, aiEditorState.chatInputValue); + } + } + + const handleOperation = async (label: 'Writer' | 'ContinueWriting' | 'MakeLonger' | 'GenerateQuiz' | 'Translate', message: string) => { + // Set selected tool + await dispatchAIEditor({ type: 'setSelectedTool', payload: label }); + + // Check what operation that was + if (label === 'Writer') { + let ai_message = ''; + let prompt = getPrompt({ label: label, selection: message }); + if (prompt) { + ai_message = await sendReqWithMessage(prompt); + await fillEditorWithText(ai_message); + } + } else if (label === 'ContinueWriting') { + let ai_message = ''; + let text_selection = getTipTapEditorSelectedText(); + let prompt = getPrompt({ label: label, selection: text_selection }); + if (prompt) { + ai_message = await sendReqWithMessage(prompt); + const message_without_original_text = await removeSentences(text_selection, ai_message); + await fillEditorWithText(message_without_original_text); + } + } else if (label === 'MakeLonger') { + // Send message to AI + // Wait for response + // Add response to editor + // Close modal + + } else if (label === 'GenerateQuiz') { + // Send message to AI + // Wait for response + // Add response to editor + // Close modal + } else if (label === 'Translate') { + // Send message to AI + // Wait for response + // Add response to editor + // Close modal + } + } + + const removeSentences = async (textToRemove: string, originalText: string) => { + const phrase = textToRemove.toLowerCase(); + const original = originalText.toLowerCase(); + + if (original.includes(phrase)) { + const regex = new RegExp(phrase, 'g'); + const newText = original.replace(regex, ''); + return newText; + } else { + return originalText; + } + } + + async function fillEditorWithText(text: string) { + const words = text.split(' '); + + for (let i = 0; i < words.length; i++) { + const textNode = { + type: 'text', + text: words[i], + }; + + props.editor.chain().focus().insertContent(textNode).run(); + + // Add a space after each word except the last one + if (i < words.length - 1) { + const spaceNode = { + type: 'text', + text: ' ', + }; + + props.editor.chain().focus().insertContent(spaceNode).run(); + } + + // Wait for 0.3 seconds before adding the next word + await new Promise(resolve => setTimeout(resolve, 120)); + } + } + + const getPrompt = (args: AIPromptsLabels) => { + const { label, selection } = args; + + if (label === 'Writer') { + return `Write 3 sentences about ${selection}`; + } else if (label === 'ContinueWriting') { + return `Continue writing 3 more sentences based on "${selection}"`; + } else if (label === 'MakeLonger') { + return `Make longer this paragraph "${selection}"`; + } else if (label === 'GenerateQuiz') { + return `Generate a quiz about "${selection}", only return an array of objects, every object should respect the following interface: + interface Answer { + answer_id: string; + answer: string; + correct: boolean; + } + interface Question { + question_id: string; + question: string; + type: "multiple_choice" + answers: Answer[]; + } + " `; + } else if (label === 'Translate') { + return `Translate ${selection} to selected language`; + } + } + + const getTipTapEditorSelectedText = () => { + const pos = props.editor.state.selection.$from.pos; // get the cursor position + const resolvedPos = props.editor.state.doc.resolve(pos); // resolve the position in the document + const start = resolvedPos.before(1); // get the start position of the node + const end = resolvedPos.after(1); // get the end position of the node + const paragraph = props.editor.state.doc.textBetween(start, end, '\n', '\n'); // get the text of the node + return paragraph; + } + + + return ( + +
+
+ +
+
+
+ +
+
+ {aiEditorState.isUserInputEnabled &&
+ +
+ +
+
} +
+
+ ) +} + +const AiEditorToolButton = (props: any) => { + const dispatchAIEditor = useAIEditorDispatch() as any; + const aiEditorState = useAIEditor() as AIEditorStateTypes; + + const handleToolButtonClick = async (label: 'Writer' | 'ContinueWriting' | 'MakeLonger' | 'GenerateQuiz' | 'Translate') => { + if (label === 'Writer') { + await dispatchAIEditor({ type: 'setSelectedTool', payload: label }); + await dispatchAIEditor({ type: 'setIsUserInputEnabled', payload: true }); + await dispatchAIEditor({ type: 'setIsFeedbackModalOpen' }); + } + if (label === 'ContinueWriting') { + await dispatchAIEditor({ type: 'setSelectedTool', payload: label }); + await dispatchAIEditor({ type: 'setIsUserInputEnabled', payload: false }); + await dispatchAIEditor({ type: 'setIsFeedbackModalOpen' }); + } + } + + return ( + + ) +} + +const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) => { + const dispatchAIEditor = useAIEditorDispatch() as any; + const aiEditorState = useAIEditor() as AIEditorStateTypes; + return ( +
+ {aiEditorState.selectedTool === 'Writer' && +
+ Write about... +
} + {aiEditorState.selectedTool === 'ContinueWriting' && +
{ + handleOperation(aiEditorState.selectedTool, aiEditorState.chatInputValue) + }} className='flex cursor-pointer space-x-1.5 p-4 items-center bg-white/10 rounded-md outline outline-1 outline-neutral-200/20 text-2xl font-semibold text-white/70 hover:bg-white/20 hover:outline-neutral-200/40 delay-75 ease-linear transition-all'> + +
} +
+ ) +} + + +export default AIEditorToolkit \ No newline at end of file diff --git a/apps/web/components/Objects/Editor/Editor.tsx b/apps/web/components/Objects/Editor/Editor.tsx index 93cac96b..6da8402e 100644 --- a/apps/web/components/Objects/Editor/Editor.tsx +++ b/apps/web/components/Objects/Editor/Editor.tsx @@ -9,6 +9,9 @@ import Image from "next/image"; import styled from "styled-components"; import { DividerVerticalIcon, SlashIcon } from "@radix-ui/react-icons"; import Avvvatars from "avvvatars-react"; +import learnhouseAI_icon from "public/learnhouse_ai_simple.png"; +import { AIEditorStateTypes, useAIEditor, useAIEditorDispatch } from "@components/Contexts/AI/AIEditorContext"; + // extensions import InfoCallout from "./Extensions/Callout/Info/InfoCallout"; import WarningCallout from "./Extensions/Callout/Warning/WarningCallout"; @@ -38,6 +41,8 @@ import java from 'highlight.js/lib/languages/java' import { CourseProvider } from "@components/Contexts/CourseContext"; import { OrgProvider } from "@components/Contexts/OrgContext"; import { useSession } from "@components/Contexts/SessionContext"; +import AIEditorTools from "./AI/AIEditorToolkit"; +import AIEditorToolkit from "./AI/AIEditorToolkit"; interface Editor { @@ -52,6 +57,9 @@ interface Editor { function Editor(props: Editor) { const session = useSession() as any; + const dispatchAIEditor = useAIEditorDispatch() as any; + const aiEditorState = useAIEditor() as AIEditorStateTypes; + // remove course_ from course_uuid const course_uuid = props.course.course_uuid.substring(7); @@ -156,19 +164,30 @@ function Editor(props: Editor) { {" "} {props.course.name} {props.activity.name}{" "} - - - - {!session.isAuthenticated && Loading} - {session.isAuthenticated && } - + +
+
+
dispatchAIEditor({ type: aiEditorState.isModalOpen ? 'setIsModalClose' : 'setIsModalOpen' })} + style={{ + background: 'conic-gradient(from 32deg at 53.75% 50%, rgb(35, 40, 93) 4deg, rgba(20, 0, 52, 0.95) 59deg, rgba(164, 45, 238, 0.88) 281deg)', + }} + className="rounded-md px-3 py-2 drop-shadow-md flex items-center space-x-1.5 text-sm text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out hover:scale-105"> + {" "} + + + {" "} + AI Editor +
+
+
- +
props.setContent(editor.getJSON())}> Save
@@ -178,6 +197,13 @@ function Editor(props: Editor) {
+ + + + {!session.isAuthenticated && Loading} + {session.isAuthenticated && } + +
@@ -193,6 +219,7 @@ function Editor(props: Editor) { exit={{ opacity: 0 }} > + diff --git a/apps/web/components/Objects/Editor/Toolbar/ToolbarButtons.tsx b/apps/web/components/Objects/Editor/Toolbar/ToolbarButtons.tsx index 3e910195..65e53b23 100644 --- a/apps/web/components/Objects/Editor/Toolbar/ToolbarButtons.tsx +++ b/apps/web/components/Objects/Editor/Toolbar/ToolbarButtons.tsx @@ -1,15 +1,15 @@ import styled from "styled-components"; -import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon, DividerVerticalIcon, ListBulletIcon } from "@radix-ui/react-icons"; -import { AlertCircle, AlertTriangle, BadgeHelp, Code, FileText, GraduationCap, HelpCircle, ImagePlus, Info, ListChecks, Sigma, Video, Youtube } from "lucide-react"; +import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, DividerVerticalIcon, ListBulletIcon } from "@radix-ui/react-icons"; +import { AlertCircle, AlertTriangle, BadgeHelp, Code, FileText, ImagePlus, Sigma, Video, Youtube } from "lucide-react"; import ToolTip from "@components/StyledElements/Tooltip/Tooltip"; export const ToolbarButtons = ({ editor, props }: any) => { + if (!editor) { return null; } // YouTube extension - const addYoutubeVideo = () => { const url = prompt("Enter YouTube URL"); diff --git a/apps/web/public/learnhouse_ai_simple_colored.png b/apps/web/public/learnhouse_ai_simple_colored.png new file mode 100644 index 0000000000000000000000000000000000000000..af78fbc415c86369f0da7a26ad23893947e96ad9 GIT binary patch literal 29183 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

*?!Y*(f;y%C*~Aj0c7!Vw(d9 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 a?SBA}`wr}dr~jn@0000