feat: init ai editor writer and continue writing feature

This commit is contained in:
swve 2024-01-11 01:39:43 +01:00
parent 21f1f2fd94
commit 5b79cbed89
12 changed files with 446 additions and 29 deletions

View file

@ -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})

View file

@ -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 (
<EditorOptionsProvider options={{ isEditable: true }}>
<AIEditorProvider>
<SessionProvider>
<EditorWrapper org={org} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>
</SessionProvider>
</AIEditorProvider>
</EditorOptionsProvider>
);
}

View file

@ -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">
{" "}
<i>
<Image width={20} src={learnhouseAI_icon} alt="" />
<Image className='outline outline-1 outline-neutral-200/20 rounded-md' width={20} src={learnhouseAI_icon} alt="" />
</i>{" "}
<i className="not-italic text-xs font-bold">Ask AI</i>
</div>
@ -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) {
</div>
<div className={`flex space-x-2 items-center -ml-[100px] ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''}`}>
<Image className={`${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''}`} width={24} src={learnhouseAI_icon} alt="" />
<Image className={`outline outline-1 outline-neutral-200/20 rounded-lg ${aiChatBotState.isWaitingForResponse ? 'animate-pulse' : ''}`} width={24} src={learnhouseAI_icon} alt="" />
<span className='text-sm font-semibold text-white/70'> AI</span>
</div>
<div className='bg-white/5 text-white/40 py-0.5 px-3 flex space-x-1 rounded-full items-center'>

View file

@ -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;

View file

@ -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 (
<AIEditorContext.Provider value={aIEditorState}>
<AIEditorDispatchContext.Provider value={dispatchAIEditor}>
{children}
</AIEditorDispatchContext.Provider>
</AIEditorContext.Provider>
)
}
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}`)
}
}

View file

@ -21,7 +21,7 @@ function AICanvaToolkit(props: AICanvaToolkitProps) {
<div style={{ background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(105.16% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgba(2, 1, 25, 0.98)' }}
className='py-1 h-10 px-2 w-max text-white rounded-xl shadow-md cursor-pointer flex items-center space-x-2 antialiased'
>
<div className='flex w-full space-x-1 font-bold text-white/80'><Image width={24} src={learnhouseAI_icon} alt="" /> <div>AI</div> </div>
<div className='flex w-full space-x-2 font-bold text-white/80'><Image className='outline outline-1 outline-neutral-200/10 rounded-lg' width={24} src={learnhouseAI_icon} alt="" /> <div>AI</div> </div>
<div>
<MoreVertical className='text-white/50' size={12} />
</div>
@ -39,7 +39,6 @@ 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();
@ -47,7 +46,6 @@ function AIActionButton(props: { editor: Editor, label: string, activity: any })
dispatchAIChatBot({ type: 'setIsModalOpen' });
await sendMessage(prompt);
}
const getTipTapEditorSelectedText = () => {

View file

@ -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 (
<AnimatePresence>
{aiEditorState.isModalOpen && <motion.div
initial={{ y: 20, opacity: 0.3, filter: 'blur(5px)' }}
animate={{ y: 0, opacity: 1, filter: 'blur(0px)' }}
exit={{ y: 50, opacity: 0, filter: 'blur(3px)' }}
transition={{ type: "spring", bounce: 0.35, duration: 1.7, mass: 0.2, velocity: 2 }}
className='fixed top-0 left-0 w-full h-full z-50 flex justify-center items-center '
style={{ pointerEvents: 'none' }}
>
<>
{aiEditorState.isFeedbackModalOpen && <UserFeedbackModal activity={props.activity} editor={props.editor} />}
<div
style={{
pointerEvents: 'auto',
background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(105.16% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgb(2 1 25 / 98%)'
}}
className="z-50 rounded-2xl max-w-screen-2xl my-10 mx-auto w-fit fixed bottom-0 left-1/2 transform -translate-x-1/2 shadow-xl ring-1 ring-inset ring-white/10 text-white p-3 flex-col-reverse backdrop-blur-md">
<div className='flex space-x-2'>
<div className='pr-1'>
<div className='flex w-full space-x-2 font-bold text-white/80 items-center'>
<Image className='outline outline-1 outline-neutral-200/20 rounded-lg' width={24} src={learnhouseAI_icon} alt="" />
<div >AI Editor</div>
<MoreVertical className='text-white/50' size={12} />
</div>
</div>
<div className='tools flex space-x-2'>
<AiEditorToolButton label='Writer' />
<AiEditorToolButton label='ContinueWriting' />
<AiEditorToolButton label='MakeLonger' />
<AiEditorToolButton label='GenerateQuiz' />
<AiEditorToolButton label='Translate' />
</div>
<div className='flex space-x-2 items-center'>
<X onClick={() => 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' />
</div>
</div>
</div></>
</motion.div>}
</AnimatePresence>
)
}
const UserFeedbackModal = (props: AIEditorToolkitProps) => {
const dispatchAIEditor = useAIEditorDispatch() as any;
const aiEditorState = useAIEditor() as AIEditorStateTypes;
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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 (
<motion.div
initial={{ y: 20, opacity: 0.3, filter: 'blur(5px)' }}
animate={{ y: 0, opacity: 1, filter: 'blur(0px)' }}
exit={{ y: 50, opacity: 0, filter: 'blur(3px)' }}
transition={{ type: "spring", bounce: 0.35, duration: 1.7, mass: 0.2, velocity: 2 }}
className='fixed top-0 left-0 w-full h-full z-50 flex justify-center items-center '
style={{ pointerEvents: 'none' }}
>
<div
style={{
pointerEvents: 'auto',
background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%), radial-gradient(105.16% 105.16% at 50% -5.16%, rgba(255, 255, 255, 0.18) 0%, rgba(0, 0, 0, 0) 100%), rgb(2 1 25 / 90%)'
}}
className="z-50 rounded-2xl max-w-screen-2xl my-10 mx-auto w-[500px] h-[200px] fixed bottom-16 left-1/2 transform -translate-x-1/2 shadow-xl ring-1 ring-inset ring-white/10 text-white p-3 flex-col-reverse backdrop-blur-md">
<div className='flex space-x-2 justify-center'>
<Image className='outline outline-1 outline-neutral-200/20 rounded-lg' width={24} src={learnhouseAI_icon} alt="" />
</div>
<div className='flex h-[115px] justify-center mx-auto antialiased'>
<div className='flex items-center justify-center '>
<AiEditorActionScreen handleOperation={handleOperation} />
</div>
</div>
{aiEditorState.isUserInputEnabled && <div className="flex items-center space-x-2 cursor-pointer">
<input onKeyDown={handleKeyPress} value={aiEditorState.chatInputValue} onChange={handleChange} placeholder='Ask AI' className='ring-1 ring-inset ring-white/20 w-full bg-gray-950/20 rounded-lg outline-none px-4 py-2 text-white text-sm placeholder:text-white/30'></input>
<div className='bg-white/10 px-3 rounded-md outline outline-1 outline-neutral-200/20 py-2 hover:bg-white/20 hover:outline-neutral-200/40 delay-75 ease-linear transition-all'>
<BetweenHorizontalStart size={20} className='text-white/50 hover:cursor-pointer' />
</div>
</div>}
</div>
</motion.div>
)
}
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 (
<button onClick={() => handleToolButtonClick(props.label)} className='flex space-x-1.5 items-center bg-white/10 px-2 py-0.5 rounded-md outline outline-1 outline-neutral-200/20 text-sm font-semibold text-white/70 hover:bg-white/20 hover:outline-neutral-200/40 delay-75 ease-linear transition-all'>
{props.label === 'Writer' && <Feather size={14} />}
{props.label === 'ContinueWriting' && <FastForward size={14} />}
{props.label === 'MakeLonger' && <FileStack size={14} />}
{props.label === 'GenerateQuiz' && <HelpCircle size={14} />}
{props.label === 'Translate' && <Languages size={14} />}
<span>{props.label}</span>
</button>
)
}
const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) => {
const dispatchAIEditor = useAIEditorDispatch() as any;
const aiEditorState = useAIEditor() as AIEditorStateTypes;
return (
<div>
{aiEditorState.selectedTool === 'Writer' &&
<div className='text-xl text-white/80 font-extrabold space-x-2'>
<span>Write about...</span>
</div>}
{aiEditorState.selectedTool === 'ContinueWriting' &&
<div onClick={() => {
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'>
<FastForward size={24} />
</div>}
</div>
)
}
export default AIEditorToolkit

View file

@ -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) {
{" "}
<b>{props.course.name}</b> <SlashIcon /> {props.activity.name}{" "}
</EditorInfoDocName>
</EditorInfoWrapper>
<EditorButtonsWrapper>
<ToolbarButtons editor={editor} />
</EditorButtonsWrapper>
</EditorDocSection>
<EditorUsersSection>
<EditorUserProfileWrapper>
{!session.isAuthenticated && <span>Loading</span>}
{session.isAuthenticated && <Avvvatars value={session.user.user_uuid} style="shape" />}
</EditorUserProfileWrapper>
<EditorUsersSection className="space-x-2">
<div>
<div className="transition-all ease-linear text-teal-100 rounded-md hover:cursor-pointer" >
<div
onClick={() => 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">
{" "}
<i>
<Image className='' width={20} src={learnhouseAI_icon} alt="" />
</i>{" "}
<i className="not-italic text-xs font-bold">AI Editor</i>
</div>
</div>
</div>
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} />
<EditorLeftOptionsSection className="space-x-2 pl-2 pr-3">
<EditorLeftOptionsSection className="space-x-2 ">
<div className="bg-sky-600 hover:bg-sky-700 transition-all ease-linear px-3 py-2 font-black text-sm shadow text-teal-100 rounded-lg hover:cursor-pointer" onClick={() => props.setContent(editor.getJSON())}> Save </div>
<ToolTip content="Preview">
<Link target="_blank" href={`/course/${course_uuid}/activity/${activity_uuid}`}>
@ -178,6 +197,13 @@ function Editor(props: Editor) {
</Link>
</ToolTip>
</EditorLeftOptionsSection>
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} />
<EditorUserProfileWrapper>
{!session.isAuthenticated && <span>Loading</span>}
{session.isAuthenticated && <Avvvatars value={session.user.user_uuid} style="shape" />}
</EditorUserProfileWrapper>
</EditorUsersSection>
</EditorTop>
</motion.div>
@ -193,6 +219,7 @@ function Editor(props: Editor) {
exit={{ opacity: 0 }}
>
<EditorContentWrapper>
<AIEditorToolkit activity={props.activity} editor={editor} />
<EditorContent editor={editor} />
</EditorContentWrapper>
</motion.div>

View file

@ -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");

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB