"use client"; import React, { useEffect, useRef, useState } from "react"; import type { LucideIcon } from "lucide-react"; import { ChevronRight, CornerDownRight, RefreshCcw, Sparkles, TriangleAlert } from "lucide-react"; // plane editor import type { EditorRefApi } from "@plane/editor"; // plane ui import { Tooltip } from "@plane/propel/tooltip"; // components import { cn } from "@plane/utils"; import { RichTextEditor } from "@/components/editor/rich-text"; // plane web constants import { AI_EDITOR_TASKS, LOADING_TEXTS } from "@/plane-web/constants/ai"; // plane web services import type { TTaskPayload } from "@/services/ai.service"; import { AIService } from "@/services/ai.service"; import { AskPiMenu } from "./ask-pi-menu"; const aiService = new AIService(); type Props = { editorRef: EditorRefApi | null; isOpen: boolean; onClose: () => void; workspaceId: string; workspaceSlug: string; }; const MENU_ITEMS: { icon: LucideIcon; key: AI_EDITOR_TASKS; label: string; }[] = [ { key: AI_EDITOR_TASKS.ASK_ANYTHING, icon: Sparkles, label: "Ask Pi", }, ]; const TONES_LIST = [ { key: "default", label: "Default", casual_score: 5, formal_score: 5, }, { key: "professional", label: "💼 Professional", casual_score: 0, formal_score: 10, }, { key: "casual", label: "😃 Casual", casual_score: 10, formal_score: 0, }, ]; export const EditorAIMenu: React.FC = (props) => { const { editorRef, isOpen, onClose, workspaceId, workspaceSlug } = props; // states const [activeTask, setActiveTask] = useState(null); const [response, setResponse] = useState(undefined); const [isRegenerating, setIsRegenerating] = useState(false); // refs const responseContainerRef = useRef(null); // params const handleGenerateResponse = async (payload: TTaskPayload) => { if (!workspaceSlug) return; await aiService.performEditorTask(workspaceSlug.toString(), payload).then((res) => setResponse(res.response)); }; // handle task click const handleClick = async (key: AI_EDITOR_TASKS) => { const selection = editorRef?.getSelectedText(); if (!selection || activeTask === key) return; setActiveTask(key); if (key === AI_EDITOR_TASKS.ASK_ANYTHING) return; setResponse(undefined); setIsRegenerating(false); await handleGenerateResponse({ task: key, text_input: selection, }); }; // handle re-generate response const handleRegenerate = async () => { const selection = editorRef?.getSelectedText(); if (!selection || !activeTask) return; setIsRegenerating(true); await handleGenerateResponse({ task: activeTask, text_input: selection, }) .then(() => responseContainerRef.current?.scrollTo({ top: 0, behavior: "smooth", }) ) .finally(() => setIsRegenerating(false)); }; // handle re-generate response const handleToneChange = async (key: string) => { const selectedTone = TONES_LIST.find((t) => t.key === key); const selection = editorRef?.getSelectedText(); if (!selectedTone || !selection || !activeTask) return; setResponse(undefined); setIsRegenerating(false); await handleGenerateResponse({ casual_score: selectedTone.casual_score, formal_score: selectedTone.formal_score, task: activeTask, text_input: selection, }).then(() => responseContainerRef.current?.scrollTo({ top: 0, behavior: "smooth", }) ); }; // handle replace selected text with the response const handleInsertText = (insertOnNextLine: boolean) => { if (!response) return; editorRef?.insertText(response, insertOnNextLine); onClose(); }; // reset on close useEffect(() => { if (!isOpen) { setActiveTask(null); setResponse(undefined); } }, [isOpen]); return (
{MENU_ITEMS.map((item) => { const isActiveTask = activeTask === item.key; return ( ); })}
{activeTask === AI_EDITOR_TASKS.ASK_ANYTHING ? ( ) : ( <>
{response ? (
) : (

{activeTask ? LOADING_TEXTS[activeTask] : "Pi is writing"}...

)}
{TONES_LIST.map((tone) => ( ))}
)}
{activeTask && (

By using this feature, you consent to sharing the message with a 3rd party service.

)}
); };