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) {
-
+
@@ -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 && }
+
+
+
+
+
+ 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 00000000..af78fbc4
Binary files /dev/null and b/apps/web/public/learnhouse_ai_simple_colored.png differ