0.9.1 • Published 8 months ago
@studio-b3/web-core v0.9.1
Studio B3 Editor
Installation
npm install -g @studio-b3/web-core
Usage
Normal
import { ArticlePrompts, setupExtensions, PromptsManager, MenuBubble, AiActionExecutor, ToolbarMenu, AdviceView, OutputForm, ChangeForm, FacetType, } from "@studio-b3/web-core";
// /...
<div className={"editor-main p-4 bg-white"}>
{editor && showToolbar && <ToolbarMenu className={"toolbar-menu"} editor={editor} />}
<EditorContent editor={editor} />
<div>{editor && <MenuBubble editor={editor} customActions={getCustomActions()} />}</div>
</div>
Our Example
"use client";
import React, { useCallback, useEffect } from "react";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
import { ArticlePrompts, setupExtensions, PromptsManager, MenuBubble, AiActionExecutor, ToolbarMenu, AdviceView, OutputForm, ChangeForm, FacetType, } from "@studio-b3/web-core";
import { Editor, EditorContent, useEditor } from "@tiptap/react";
import MarkdownIt from "markdown-it";
import { useDebounce } from "use-debounce";
import { Theme } from "@radix-ui/themes";
import { DOMSerializer } from "prosemirror-model";
import TurndownService from "turndown";
import { Markdown } from "tiptap-markdown";
const md = new MarkdownIt();
export interface EditorWrapperProps {
value: string;
onChange?: (e: any) => void;
placeholder?: string;
showToolbar?: boolean;
customBubbleActions?: FeanorEditorAction[];
customSlashActions?: FeanorEditorAction[];
}
export interface FeanorEditorAction {
name: string;
action?: (editor: Editor) => Promise<void>;
}
export default function B3EditorWrapper({
value,
onChange,
placeholder,
showToolbar,
customBubbleActions,
customSlashActions
}: EditorWrapperProps) {
const actionExecutor: AiActionExecutor = new AiActionExecutor();
actionExecutor.setEndpointUrl("/api/chat");
const instance = PromptsManager.getInstance();
const map = customSlashActions?.map((action) => {
return {
name: action.name,
i18Name: false,
template: `123125`,
facetType: FacetType.SLASH_COMMAND,
outputForm: OutputForm.STREAMING,
action: async (editor: Editor) => {
if (action.action) {
await action.action(editor);
}
},
};
}) || [];
instance.updateActionsMap("article", ArticlePrompts.concat(map));
const editor = useEditor({
extensions: setupExtensions(instance, actionExecutor).concat([
Markdown.configure({
transformPastedText: true,
transformCopiedText: false,
}),
]),
content: md.render(value),
immediatelyRender: false,
editorProps: {
attributes: {
class: "prose lg:prose-xl bb-editor-inner",
},
},
onUpdate: ({ editor }) => {
if (onChange) {
const schema = editor.state.schema;
try {
const serializer = DOMSerializer.fromSchema(schema);
const serialized: HTMLElement | DocumentFragment = serializer.serializeFragment(editor.state.doc.content);
const html: string = Array.from(serialized.childNodes)
.map((node: ChildNode) => (node as HTMLElement).outerHTML)
.join("");
const turndownService = new TurndownService();
const markdown = turndownService.turndown(html);
onChange(markdown);
} catch (e) {
console.error(e);
}
}
},
});
const [debouncedEditor] = useDebounce(editor?.state.doc.content, 5000);
useEffect(() => {
if (debouncedEditor) {
try {
localStorage.setItem("editor", JSON.stringify(editor?.getJSON()));
console.info("Saved to localStorage");
} catch (e) {
console.error("Error saving to localStorage:", e);
}
}
}, [debouncedEditor, editor]);
useEffect(() => {
if (value !== "") {
editor?.commands?.setContent(md.render(value));
return;
}
const content = localStorage.getItem("editor");
if (content) {
try {
const parsed = JSON.parse(content);
parsed?.content?.forEach((item: any) => {
if (item.content) {
item.content.forEach((subItem: any) => {
if (subItem.marks) {
subItem.marks = subItem.marks.filter((mark: any) => mark.type !== "advice");
}
});
}
});
editor?.commands?.setContent(parsed);
if (parsed.content.length === 1) {
if (!parsed.content[0].content) {
editor?.commands?.setContent(md.render(value));
}
}
} catch (e) {
editor?.commands?.setContent(md.render(value));
console.error(e);
}
}
if (!!editor) {
actionExecutor.setEditor(editor);
}
}, [editor]);
const getCustomActions = useCallback(() => (customBubbleActions || []).map((action) => {
return {
name: action.name,
template: "",
facetType: FacetType.BUBBLE_MENU,
changeForm: ChangeForm.DIFF,
outputForm: OutputForm.TEXT,
action: async () => {
if (action.action) {
await action.action(editor!);
}
},
};
}), [customBubbleActions, editor]);
return (
<div>
<Theme className={"w-full flex editor-block"}>
<div className={"w-full editor-section"}>
<div className={"editor-main p-4 bg-white"}>
{editor && showToolbar && <ToolbarMenu className={"toolbar-menu"} editor={editor} />}
<EditorContent editor={editor} />
<div>{editor && <MenuBubble editor={editor} customActions={getCustomActions()} />}</div>
</div>
</div>
{editor && <AdviceView style={{ height: "auto" }} editor={editor} />}
</Theme>
</div>
);
}
Custom Menu examples
const BubbleMenu: PromptAction[] = [
{
name: 'Polish',
i18Name: true,
template: `You are an assistant helping to polish sentence. Output in markdown format. \n ###${DefinedVariable.SELECTION}###`,
facetType: FacetType.BUBBLE_MENU,
outputForm: OutputForm.STREAMING,
},
{
name: 'Similar Chunk',
i18Name: true,
template: `You are an assistant helping to find similar content. Output in markdown format. \n ###${DefinedVariable.SELECTION}###`,
facetType: FacetType.BUBBLE_MENU,
outputForm: OutputForm.STREAMING,
},
{
name: 'Simplify Content',
i18Name: true,
template: `You are an assistant helping to simplify content. Output in markdown format. \n ###${DefinedVariable.SELECTION}###`,
facetType: FacetType.BUBBLE_MENU,
outputForm: OutputForm.STREAMING,
changeForm: ChangeForm.DIFF,
},
];
Custom Smamples:
const actionExecutor: AiActionExecutor = new AiActionExecutor();
actionExecutor.setEndpointUrl("/api/chat");
const instance = PromptsManager.getInstance();
const map = customSlashActions?.map((action) => {
return {
name: action.name,
i18Name: false,
template: `123125`,
facetType: FacetType.SLASH_COMMAND,
outputForm: OutputForm.STREAMING,
action: async (editor: Editor) => {
if (action.action) {
await action.action(editor);
}
},
};
}) || [];
instance.updateActionsMap("article", ArticlePrompts.concat(map));
const editor = useEditor({
extensions: setupExtensions(instance, actionExecutor).concat([
Markdown.configure({
transformPastedText: true,
transformCopiedText: false,
}),
]),
content: md.render(value),
immediatelyRender: false,
editorProps: {
attributes: {
class: "prose lg:prose-xl bb-editor-inner",
},
},
onUpdate: ({ editor }) => {
if (onChange) {
const schema = editor.state.schema;
try {
const serializer = DOMSerializer.fromSchema(schema);
const serialized: HTMLElement | DocumentFragment = serializer.serializeFragment(editor.state.doc.content);
const html: string = Array.from(serialized.childNodes)
.map((node: ChildNode) => (node as HTMLElement).outerHTML)
.join("");
const turndownService = new TurndownService();
const markdown = turndownService.turndown(html);
onChange(markdown);
} catch (e) {
console.error(e);
}
}
},
});
0.9.1-SNAPSHOT-1
8 months ago
0.9.1-RC-1
8 months ago
0.9.1-SNAPSHOT
8 months ago
0.9.0
8 months ago
0.9.1
8 months ago
0.8.9
8 months ago
0.8.8
8 months ago
0.8.5
8 months ago
0.8.4
8 months ago
0.8.7
8 months ago
0.8.6
8 months ago
0.2.14
9 months ago
0.8.1
9 months ago
0.8.3
9 months ago
0.8.2
9 months ago
0.2.13
10 months ago
0.2.12
10 months ago
0.2.11
10 months ago
0.2.10
10 months ago
0.0.3
10 months ago
0.0.2
10 months ago
0.0.8
10 months ago
0.2.9
10 months ago
0.2.8
10 months ago
0.0.5
10 months ago
0.0.4
10 months ago
0.0.7
10 months ago
0.0.6
10 months ago
0.0.1
2 years ago