mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: org wide ai features check
This commit is contained in:
parent
de93d56945
commit
077c26ce15
24 changed files with 573 additions and 163 deletions
39
apps/web/components/AI/Hooks/useGetAIFeatures.tsx
Normal file
39
apps/web/components/AI/Hooks/useGetAIFeatures.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import React from 'react'
|
||||
|
||||
interface UseGetAIFeatures {
|
||||
feature: 'editor' | 'activity_ask' | 'course_ask' | 'global_ai_ask',
|
||||
}
|
||||
|
||||
|
||||
function useGetAIFeatures(props: UseGetAIFeatures) {
|
||||
const org = useOrg() as any
|
||||
const [isEnabled, setisEnabled] = React.useState(false)
|
||||
|
||||
function checkAvailableAIFeaturesOnOrg(feature: string) {
|
||||
const config = org.config.config.AIConfig;
|
||||
if (!config.enabled) {
|
||||
console.log("AI is not enabled for this Organization.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.features[feature]) {
|
||||
console.log(`Feature ${feature} is not enabled for this Organization.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (org) { // Check if org is not null or undefined
|
||||
let isEnabledStatus = checkAvailableAIFeaturesOnOrg(props.feature)
|
||||
setisEnabled(isEnabledStatus)
|
||||
}
|
||||
}, [org])
|
||||
|
||||
return isEnabled
|
||||
|
||||
}
|
||||
|
||||
export default useGetAIFeatures
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
'use client';
|
||||
import { AIMessage } from '@components/AI/AIActivityAsk';
|
||||
import { AIMessage } from '@components/Objects/Activities/AI/AIActivityAsk';
|
||||
import React, { createContext, useContext, useReducer } from 'react'
|
||||
export const AIChatBotContext = createContext(null) as any;
|
||||
export const AIChatBotDispatchContext = createContext(null) as any;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client';
|
||||
import { AIMessage } from '@components/AI/AIActivityAsk';
|
||||
import { AIMessage } from '@components/Objects/Activities/AI/AIActivityAsk';
|
||||
import React, { createContext, useContext, useReducer } from 'react'
|
||||
export const AIEditorContext = createContext(null) as any;
|
||||
export const AIEditorDispatchContext = createContext(null) as any;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ import Image from 'next/image';
|
|||
import { send } from 'process';
|
||||
import learnhouseAI_icon from "public/learnhouse_ai_simple.png";
|
||||
import learnhouseAI_logo_black from "public/learnhouse_ai_black_logo.png";
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import React, { use, useEffect, useRef } from 'react'
|
||||
import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@components/Contexts/AI/AIChatBotContext';
|
||||
import FeedbackModal from '@components/Objects/Modals/Feedback/Feedback';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import useGetAIFeatures from '../../../AI/Hooks/useGetAIFeatures';
|
||||
|
||||
|
||||
type AIActivityAskProps = {
|
||||
|
|
@ -20,25 +21,38 @@ type AIActivityAskProps = {
|
|||
|
||||
|
||||
function AIActivityAsk(props: AIActivityAskProps) {
|
||||
|
||||
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'activity_ask' });
|
||||
const [isButtonAvailable, setIsButtonAvailable] = React.useState(false);
|
||||
const dispatchAIChatBot = useAIChatBotDispatch() as any;
|
||||
|
||||
useEffect(() => {
|
||||
if (is_ai_feature_enabled) {
|
||||
setIsButtonAvailable(true);
|
||||
}
|
||||
}
|
||||
, [is_ai_feature_enabled]);
|
||||
|
||||
return (
|
||||
<div className=''>
|
||||
<ActivityChatMessageBox activity={props.activity} />
|
||||
<div
|
||||
onClick={() => dispatchAIChatBot({ type: '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-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 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>
|
||||
</div>
|
||||
<>
|
||||
{isButtonAvailable && (
|
||||
<div >
|
||||
<ActivityChatMessageBox activity={props.activity} />
|
||||
<div
|
||||
onClick={() => dispatchAIChatBot({ type: '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-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 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>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ import { BubbleMenu } from '@tiptap/react';
|
|||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip';
|
||||
import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@components/Contexts/AI/AIChatBotContext';
|
||||
import { sendActivityAIChatMessage, startActivityAIChatSession } from '@services/ai/ai';
|
||||
import useGetAIFeatures from '../../../../AI/Hooks/useGetAIFeatures';
|
||||
|
||||
|
||||
|
||||
|
|
@ -16,23 +17,35 @@ type AICanvaToolkitProps = {
|
|||
}
|
||||
|
||||
function AICanvaToolkit(props: AICanvaToolkitProps) {
|
||||
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'activity_ask' });
|
||||
const [isBubbleMenuAvailable, setIsButtonAvailable] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (is_ai_feature_enabled) {
|
||||
setIsButtonAvailable(true);
|
||||
}
|
||||
}, [is_ai_feature_enabled])
|
||||
|
||||
|
||||
return (
|
||||
<BubbleMenu className="w-fit" tippyOptions={{ duration: 100 }} editor={props.editor}>
|
||||
<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-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} />
|
||||
<>
|
||||
{isBubbleMenuAvailable && <BubbleMenu className="w-fit" tippyOptions={{ duration: 100 }} editor={props.editor}>
|
||||
<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-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>
|
||||
<div className='flex space-x-2'>
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Explain' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Summarize' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Translate' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Examples' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex space-x-2'>
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Explain' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Summarize' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Translate' />
|
||||
<AIActionButton editor={props.editor} activity={props.activity} label='Examples' />
|
||||
</div>
|
||||
</div>
|
||||
</BubbleMenu>
|
||||
</BubbleMenu>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +58,7 @@ function AIActionButton(props: { editor: Editor, label: string, activity: any })
|
|||
const prompt = getPrompt(label, selection);
|
||||
dispatchAIChatBot({ type: 'setIsModalOpen' });
|
||||
await sendMessage(prompt);
|
||||
|
||||
|
||||
}
|
||||
|
||||
const getTipTapEditorSelectedText = () => {
|
||||
|
|
@ -24,7 +24,7 @@ import python from 'highlight.js/lib/languages/python'
|
|||
import java from 'highlight.js/lib/languages/java'
|
||||
import { NoTextInput } from "@components/Objects/Editor/Extensions/NoTextInput/NoTextInput";
|
||||
import EditorOptionsProvider from "@components/Contexts/Editor/EditorContext";
|
||||
import AICanvaToolkit from "./Elements/AICanvaToolkit";
|
||||
import AICanvaToolkit from "./AI/AICanvaToolkit";
|
||||
|
||||
|
||||
interface Editor {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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';
|
||||
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures';
|
||||
|
||||
type AIEditorToolkitProps = {
|
||||
editor: Editor,
|
||||
|
|
@ -22,48 +23,61 @@ type AIPromptsLabels = {
|
|||
function AIEditorToolkit(props: AIEditorToolkitProps) {
|
||||
const dispatchAIEditor = useAIEditorDispatch() as any;
|
||||
const aiEditorState = useAIEditor() as AIEditorStateTypes;
|
||||
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'editor' });
|
||||
const [isToolkitAvailable, setIsToolkitAvailable] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (is_ai_feature_enabled) {
|
||||
setIsToolkitAvailable(true);
|
||||
}
|
||||
}, [is_ai_feature_enabled])
|
||||
|
||||
|
||||
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' />
|
||||
<>
|
||||
{isToolkitAvailable && <div className='flex space-x-2'>
|
||||
<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='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>
|
||||
</div>}
|
||||
</>
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,9 @@ import html from 'highlight.js/lib/languages/xml'
|
|||
import python from 'highlight.js/lib/languages/python'
|
||||
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";
|
||||
import useGetAIFeatures from "@components/AI/Hooks/useGetAIFeatures";
|
||||
|
||||
|
||||
interface Editor {
|
||||
|
|
@ -59,6 +58,14 @@ function Editor(props: Editor) {
|
|||
const session = useSession() as any;
|
||||
const dispatchAIEditor = useAIEditorDispatch() as any;
|
||||
const aiEditorState = useAIEditor() as AIEditorStateTypes;
|
||||
const is_ai_feature_enabled = useGetAIFeatures({ feature: 'editor' });
|
||||
const [isButtonAvailable, setIsButtonAvailable] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (is_ai_feature_enabled) {
|
||||
setIsButtonAvailable(true);
|
||||
}
|
||||
}, [is_ai_feature_enabled])
|
||||
|
||||
// remove course_ from course_uuid
|
||||
const course_uuid = props.course.course_uuid.substring(7);
|
||||
|
|
@ -137,7 +144,6 @@ function Editor(props: Editor) {
|
|||
|
||||
return (
|
||||
<Page>
|
||||
<OrgProvider orgslug={props.org?.slug}>
|
||||
<CourseProvider courseuuid={props.course.course_uuid}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
|
|
@ -172,7 +178,7 @@ function Editor(props: Editor) {
|
|||
<EditorUsersSection className="space-x-2">
|
||||
<div>
|
||||
<div className="transition-all ease-linear text-teal-100 rounded-md hover:cursor-pointer" >
|
||||
<div
|
||||
{isButtonAvailable && <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)',
|
||||
|
|
@ -183,7 +189,7 @@ function Editor(props: Editor) {
|
|||
<Image className='' width={20} src={learnhouseAI_icon} alt="" />
|
||||
</i>{" "}
|
||||
<i className="not-italic text-xs font-bold">AI Editor</i>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey", opacity: '0.5' }} />
|
||||
|
|
@ -224,7 +230,6 @@ function Editor(props: Editor) {
|
|||
</EditorContentWrapper>
|
||||
</motion.div>
|
||||
</CourseProvider>
|
||||
</OrgProvider>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Editor from "./Editor";
|
|||
import { updateActivity } from "@services/courses/activities";
|
||||
import { toast } from "react-hot-toast";
|
||||
import Toast from "@components/StyledElements/Toast/Toast";
|
||||
import { OrgProvider } from "@components/Contexts/OrgContext";
|
||||
|
||||
interface EditorWrapperProps {
|
||||
content: string;
|
||||
|
|
@ -26,7 +27,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
|||
// setProviderState(provider);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -50,8 +51,9 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
|||
} else {
|
||||
return <>
|
||||
<Toast></Toast>
|
||||
<Editor org={props.org} course={props.course} activity={props.activity} content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>;
|
||||
|
||||
<OrgProvider orgslug={props.org.slug}>
|
||||
<Editor org={props.org} course={props.course} activity={props.activity} content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>;
|
||||
</OrgProvider>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue