diff --git a/web3/apps/web/src/views/tiptap/index.vue b/web3/apps/web/src/views/tiptap/index.vue
index 1ece3f4f..36e88aca 100644
--- a/web3/apps/web/src/views/tiptap/index.vue
+++ b/web3/apps/web/src/views/tiptap/index.vue
@@ -1,15 +1,23 @@
-
diff --git a/web3/packages/tiptap/src/components/useEditorConfig.ts b/web3/packages/tiptap/src/components/useEditorConfig.ts
deleted file mode 100644
index 452806d9..00000000
--- a/web3/packages/tiptap/src/components/useEditorConfig.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-import { computed } from "vue";
-import {
- Bold,
- BulletList,
- Italic,
- BaseKit,
- Underline,
- Strike,
- LineHeight,
- Image,
- History,
- Heading,
- CodeBlock,
- FontSize,
- Highlight,
- Table,
- Clear,
- Blockquote,
- Link,
- Color,
- Video,
- OrderedList,
- HorizontalRule,
- Fullscreen,
- TaskList,
- MoreMark,
- FormatPainter,
- SlashCommand,
- Indent,
- ImportWord,
- Columns,
- TextAlign,
- ImageUpload,
- VideoUpload,
- FontFamily,
- FindAndReplace,
- Code,
- AI,
- Preview,
- Printer,
- Iframe,
- SpecialCharacter,
- SourceCode,
-} from "echo-editor";
-import { ExportWord } from "./ExportWord";
-import OpenAI from "openai";
-
-export function useEditorConfig(minimal: boolean) {
- const extensions = computed(() =>
- minimal ? minimalExtensions : fullExtensions
- );
-
- const minimalExtensions = [
- BaseKit.configure({
- characterCount: {
- limit: 50000,
- },
- }),
- Heading,
- Bold.configure({ spacer: true }),
- Italic,
- Underline,
- HorizontalRule,
- TextAlign.configure({
- types: ["heading", "paragraph", "image"],
- spacer: true,
- }),
- Image,
- Blockquote.configure({ spacer: true }),
- Code,
- Link,
- Color,
- TaskList.configure({ spacer: true }),
- OrderedList,
- BulletList,
- ];
- const fullExtensions = [
- BaseKit.configure({
- placeholder: {
- showOnlyCurrent: true,
- },
- characterCount: {
- limit: 50000,
- },
- }),
- History,
- Columns,
- FormatPainter,
- Clear,
- Heading.configure({ spacer: true }),
- FontSize,
- FontFamily,
- Bold,
- Italic,
- Underline,
- Strike,
- MoreMark,
- Color.configure({ spacer: true }),
- Highlight,
- BulletList,
- OrderedList,
- TextAlign.configure({
- types: ["heading", "paragraph", "image"],
- spacer: true,
- }),
- Indent,
- LineHeight,
- TaskList.configure({
- spacer: true,
- taskItem: {
- nested: true,
- },
- }),
- Link,
- Image,
- ImageUpload.configure({
- upload: (files: File) => {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(URL.createObjectURL(files));
- }, 3000);
- });
- },
- }),
- Video,
- VideoUpload.configure({
- upload: handleFileUpload,
- }),
- Blockquote,
- SlashCommand,
- HorizontalRule,
- CodeBlock,
- Table.configure({ spacer: true }),
- Code,
- ExportWord,
- AI.configure({
- completions: AICompletions,
- shortcuts: [
- // 这里可以传入额外的自定义shortcuts
- {
- label: "Custom Actions",
- children: [
- {
- label: "This is Custom Action",
- prompt:
- "Rewrite this content with no spelling mistakes, proper grammar, and with more descriptive language, using best writing practices without losing the original meaning.",
- },
- ],
- },
- ],
- }),
- ImportWord.configure({
- upload: handleFileUpload,
- }),
- SpecialCharacter,
- Fullscreen.configure({ spacer: true }),
- SourceCode,
- Preview,
- FindAndReplace.configure({ spacer: true }),
- Printer,
- Iframe,
- ];
- async function handleFileUpload(files: File[]) {
- const f = files.map((file) => ({
- src: URL.createObjectURL(file),
- alt: file.name,
- }));
- return Promise.resolve(f);
- }
-
- async function AICompletions(
- history: Array<{ role: string; content: string }> = [],
- signal?: AbortSignal
- ) {
- // groq.com For free llm api recommend deepseek r1 70b
- // SECURITY WARNING: API keys should never be exposed in the frontend
- // This is just for demo purposes
- // In production, implement this through your backend API
- const apiKey = "import.meta.env.VITE_OPENAI_API_KEY";
- const baseURL = "import.meta.env.VITE_OPENAI_BASE_URL";
- const model = "import.meta.env.VITE_OPENAI_MODEL";
-
- if (!apiKey || !baseURL || !model) {
- throw new Error(
- "OpenAI configuration is missing. Please check your environment variables."
- );
- }
-
- const openai = new OpenAI({
- apiKey: apiKey,
- dangerouslyAllowBrowser: true,
- baseURL: baseURL,
- });
-
- const systemMsg = `You are a professional writing assistant. Please respond based on the user's context:
-
-1. Maintain a professional, accurate, and objective tone
-2. Ensure responses are clear, coherent, and well-structured
-3. Responses must be in HTML format, preserving all HTML tags, links, and styles
-4. Support the following writing enhancements:
- - Grammar and spelling corrections
- - Improved sentence structure and expression
- - Optimized article formatting and layout
- - Maintain the core meaning of the original text
-5. If context includes code, maintain code formatting and provide optimization suggestions
-6. Add appropriate HTML elements like headings, lists, quotes etc. to enhance readability as needed
-
-Please respond only based on the provided context, do not add irrelevant information.`;
-
- const systemPrompt = [{ role: "system", content: systemMsg }];
- const finalMessages = [...systemPrompt];
-
- if (history.length > 0) {
- finalMessages.push(...history);
- }
-
- try {
- const stream = await openai.chat.completions.create(
- {
- model,
- messages: finalMessages,
- temperature: 0.7,
- stream: true,
- reasoning_format: "parsed", // groq deepseek r1 need this
- } as any,
- { signal }
- );
-
- return stream;
- } catch (error) {
- if (error instanceof Error) {
- console.error("Error in AI Completions:", error.message);
- }
- throw error;
- }
- }
-
- return {
- extensions,
- };
-}
diff --git a/web3/packages/tiptap/src/context/consts.ts b/web3/packages/tiptap/src/context/consts.ts
new file mode 100644
index 00000000..0c717a28
--- /dev/null
+++ b/web3/packages/tiptap/src/context/consts.ts
@@ -0,0 +1,232 @@
+import { ExportWord } from "../components/ExportWord";
+import OpenAI from "openai";
+
+import {
+ Bold,
+ BulletList,
+ Italic,
+ BaseKit,
+ Underline,
+ Strike,
+ LineHeight,
+ Image,
+ History,
+ Heading,
+ CodeBlock,
+ FontSize,
+ Highlight,
+ Table,
+ Clear,
+ Blockquote,
+ Link,
+ Color,
+ Video,
+ OrderedList,
+ HorizontalRule,
+ Fullscreen,
+ TaskList,
+ MoreMark,
+ FormatPainter,
+ SlashCommand,
+ Indent,
+ ImportWord,
+ Columns,
+ TextAlign,
+ ImageUpload,
+ VideoUpload,
+ FontFamily,
+ FindAndReplace,
+ Code,
+ AI,
+ Preview,
+ Printer,
+ Iframe,
+ SpecialCharacter,
+ SourceCode,
+} from "echo-editor";
+
+async function handleFileUpload(files: File[]) {
+ const f = files.map((file) => ({
+ src: URL.createObjectURL(file),
+ alt: file.name,
+ }));
+ return Promise.resolve(f);
+}
+
+async function AICompletions(
+ history: Array<{ role: string; content: string }> = [],
+ signal?: AbortSignal
+) {
+ // groq.com For free llm api recommend deepseek r1 70b
+ // SECURITY WARNING: API keys should never be exposed in the frontend
+ // This is just for demo purposes
+ // In production, implement this through your backend API
+ const apiKey = "import.meta.env.VITE_OPENAI_API_KEY";
+ const baseURL = "import.meta.env.VITE_OPENAI_BASE_URL";
+ const model = "import.meta.env.VITE_OPENAI_MODEL";
+
+ if (!apiKey || !baseURL || !model) {
+ throw new Error(
+ "OpenAI configuration is missing. Please check your environment variables."
+ );
+ }
+
+ const openai = new OpenAI({
+ apiKey: apiKey,
+ dangerouslyAllowBrowser: true,
+ baseURL: baseURL,
+ });
+
+ const systemMsg = `You are a professional writing assistant. Please respond based on the user's context:
+
+1. Maintain a professional, accurate, and objective tone
+2. Ensure responses are clear, coherent, and well-structured
+3. Responses must be in HTML format, preserving all HTML tags, links, and styles
+4. Support the following writing enhancements:
+ - Grammar and spelling corrections
+ - Improved sentence structure and expression
+ - Optimized article formatting and layout
+ - Maintain the core meaning of the original text
+5. If context includes code, maintain code formatting and provide optimization suggestions
+6. Add appropriate HTML elements like headings, lists, quotes etc. to enhance readability as needed
+
+Please respond only based on the provided context, do not add irrelevant information.`;
+
+ const systemPrompt = [{ role: "system", content: systemMsg }];
+ const finalMessages = [...systemPrompt];
+
+ if (history.length > 0) {
+ finalMessages.push(...history);
+ }
+
+ try {
+ const stream = await openai.chat.completions.create(
+ {
+ model,
+ messages: finalMessages,
+ temperature: 0.7,
+ stream: true,
+ reasoning_format: "parsed", // groq deepseek r1 need this
+ } as any,
+ { signal }
+ );
+
+ return stream;
+ } catch (error) {
+ if (error instanceof Error) {
+ console.error("Error in AI Completions:", error.message);
+ }
+ throw error;
+ }
+}
+
+export const minimalExtensions = [
+ BaseKit.configure({
+ characterCount: {
+ limit: 50000,
+ },
+ }),
+ Heading,
+ Bold.configure({ spacer: true }),
+ Italic,
+ Underline,
+ HorizontalRule,
+ TextAlign.configure({
+ types: ["heading", "paragraph", "image"],
+ spacer: true,
+ }),
+ Image,
+ Blockquote.configure({ spacer: true }),
+ Code,
+ Link,
+ Color,
+ TaskList.configure({ spacer: true }),
+ OrderedList,
+ BulletList,
+];
+export const fullExtensions = [
+ BaseKit.configure({
+ placeholder: {
+ showOnlyCurrent: true,
+ },
+ characterCount: {
+ limit: 50000,
+ },
+ }),
+ History,
+ Columns,
+ FormatPainter,
+ Clear,
+ Heading.configure({ spacer: true }),
+ FontSize,
+ FontFamily,
+ Bold,
+ Italic,
+ Underline,
+ Strike,
+ MoreMark,
+ Color.configure({ spacer: true }),
+ Highlight,
+ BulletList,
+ OrderedList,
+ TextAlign.configure({
+ types: ["heading", "paragraph", "image"],
+ spacer: true,
+ }),
+ Indent,
+ LineHeight,
+ TaskList.configure({
+ spacer: true,
+ taskItem: {
+ nested: true,
+ },
+ }),
+ Link,
+ Image,
+ ImageUpload.configure({
+ upload: (files: File) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(URL.createObjectURL(files));
+ }, 3000);
+ });
+ },
+ }),
+ Video,
+ VideoUpload.configure({
+ upload: handleFileUpload,
+ }),
+ Blockquote,
+ SlashCommand,
+ HorizontalRule,
+ CodeBlock,
+ Table.configure({ spacer: true }),
+ Code,
+ ExportWord,
+ AI.configure({
+ completions: AICompletions,
+ shortcuts: [
+ // 这里可以传入额外的自定义shortcuts
+ {
+ label: "Custom Actions",
+ children: [
+ {
+ label: "This is Custom Action",
+ prompt:
+ "Rewrite this content with no spelling mistakes, proper grammar, and with more descriptive language, using best writing practices without losing the original meaning.",
+ },
+ ],
+ },
+ ],
+ }),
+ // ImportWord.configure({
+ // upload: handleFileUpload,
+ // }),
+ SpecialCharacter,
+ Fullscreen.configure({ spacer: true }),
+ SourceCode,
+ Preview,
+ FindAndReplace.configure({ spacer: true }),
+ Printer,
+ Iframe,
+];
diff --git a/web3/packages/tiptap/src/context/useTiptapStore.ts b/web3/packages/tiptap/src/context/useTiptapStore.ts
index aa1ab113..72652520 100644
--- a/web3/packages/tiptap/src/context/useTiptapStore.ts
+++ b/web3/packages/tiptap/src/context/useTiptapStore.ts
@@ -1,4 +1,13 @@
-import { provide, inject, reactive, type InjectionKey } from "vue";
+import {
+ provide,
+ inject,
+ reactive,
+ type InjectionKey,
+ computed,
+ type ComputedRef,
+} from "vue";
+
+import { minimalExtensions, fullExtensions } from "./consts";
const TiptapStoreKey: InjectionKey = Symbol("TiptapStore");
@@ -13,6 +22,7 @@ export interface TiptapState {
export type TiptapStore = {
state: TiptapState;
+ extensions: ComputedRef;
};
export function useTiptapStore(initialState?: Partial) {
@@ -26,8 +36,13 @@ export function useTiptapStore(initialState?: Partial) {
...initialState,
});
+ const extensions = computed(() => {
+ return state.minimal ? minimalExtensions : fullExtensions;
+ });
+
const store: TiptapStore = {
state: state,
+ extensions,
};
provide(TiptapStoreKey, store);
diff --git a/web3/packages/tiptap/src/index.ts b/web3/packages/tiptap/src/index.ts
index 7ffd635d..a7f1d1fc 100644
--- a/web3/packages/tiptap/src/index.ts
+++ b/web3/packages/tiptap/src/index.ts
@@ -2,6 +2,7 @@ import TiptapEditor from "./views/index.vue";
import { useTiptapStore, useTiptapContext } from "./context/useTiptapStore";
import { ThemeToggle } from "echo-editor";
import EditorActions from "./components/EditorActions.vue";
+import { DEMO_CONTENT } from "./views/initContent";
export {
TiptapEditor,
@@ -9,4 +10,5 @@ export {
useTiptapStore,
useTiptapContext,
EditorActions,
+ DEMO_CONTENT,
};
diff --git a/web3/packages/tiptap/src/views/index.vue b/web3/packages/tiptap/src/views/index.vue
index f9cb86e7..277d7da4 100644
--- a/web3/packages/tiptap/src/views/index.vue
+++ b/web3/packages/tiptap/src/views/index.vue
@@ -1,9 +1,8 @@
-